Snap for 7814173 from 864ddfe1fe062e198cd8fa52ef3ea43720559848 to mainline-ipsec-release

Change-Id: Idf0c4ab188486c7ab4545701867e3f593141e079
diff --git a/Android.bp b/Android.bp
index 0f577a4..c40c3fb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -119,7 +119,7 @@
     sdk_version: "system_30",
     visibility: ["//visibility:private"],
     lint: {
-        baseline_filename: "lint-baseline-api-30-shims.xml",
+        strict_updatability_linting: true,
     },
 }
 
diff --git a/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
index 5825021..4c471ca 100644
--- a/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
+++ b/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
@@ -130,7 +130,7 @@
     @Override
     public CaptivePortalDataShim withVenueFriendlyName(String friendlyName)
             throws UnsupportedApiLevelException {
-        // Not supported in API level 29
+        // Not supported in API level 30
         throw new UnsupportedApiLevelException("FriendlyName not supported on API 30");
     }
 
@@ -149,7 +149,7 @@
     public CaptivePortalDataShim withPasspointInfo(@NonNull String friendlyName,
             @NonNull Uri venueInfoUrl, @NonNull Uri termsAndConditionsUrl)
             throws UnsupportedApiLevelException {
-        // Not supported in API level 29
+        // Not supported in API level 30
         throw new UnsupportedApiLevelException("PasspointInfo not supported on API 30");
     }
 }
diff --git a/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java
index 477dd42a..a73f852 100644
--- a/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java
+++ b/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.networkstack.apishim.api30;
 
+import static com.android.modules.utils.build.SdkLevel.isAtLeastR;
+
 import android.net.IpPrefix;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
@@ -25,17 +27,17 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
+import androidx.annotation.RequiresApi;
 
 import com.android.networkstack.apishim.common.CaptivePortalDataShim;
 import com.android.networkstack.apishim.common.NetworkInformationShim;
-import com.android.networkstack.apishim.common.ShimUtils;
 
 import java.net.Inet4Address;
 
 /**
  * Compatibility implementation of {@link NetworkInformationShim}.
  */
+@RequiresApi(Build.VERSION_CODES.R)
 public class NetworkInformationShimImpl extends
         com.android.networkstack.apishim.api29.NetworkInformationShimImpl {
     private static final String TAG = "api30.NetworkInformationShimImpl";
@@ -45,21 +47,14 @@
     /**
      * Get a new instance of {@link NetworkInformationShim}.
      */
+    @RequiresApi(Build.VERSION_CODES.Q)
     public static NetworkInformationShim newInstance() {
-        if (!useApiAboveQ()) {
+        if (!isAtLeastR()) {
             return com.android.networkstack.apishim.api29.NetworkInformationShimImpl.newInstance();
         }
         return new NetworkInformationShimImpl();
     }
 
-    /**
-     * Indicates whether the shim can use APIs above the Q SDK.
-     */
-    @VisibleForTesting
-    public static boolean useApiAboveQ() {
-        return ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q);
-    }
-
     @Nullable
     @Override
     public Uri getCaptivePortalApiUrl(@Nullable LinkProperties lp) {
diff --git a/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java b/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
index 0cd9f65..b151cb9 100644
--- a/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
+++ b/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
@@ -83,10 +83,6 @@
             return true;
         }
 
-        // TODO: once TRANSPORT_TEST is @SystemApi in S and S SDK is stable (so constant shims can
-        // be replaced with the SDK constant that will be inlined), replace isTestNetwork with
-        // hasTransport(TRANSPORT_TEST)
-
         // Test networks that also have one of the major transport types are attempting to replicate
         // that transport on a test interface (for example, test ethernet networks with
         // EthernetManager#setIncludeTestInterfaces). Run validation on them for realistic tests.
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index 02a39f6..c344d07 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -121,7 +121,7 @@
             enabled: false,
         },
     },
-    imports: ["ipmemorystore-aidl-interfaces"],
+    imports: ["ipmemorystore-aidl-interfaces-V10"],
     versions: [
         "1",
         "2",
diff --git a/lint-baseline-api-30-shims.xml b/lint-baseline-api-30-shims.xml
deleted file mode 100644
index da541cd..0000000
--- a/lint-baseline-api-30-shims.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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="Call requires API level R (current min is 29): `android.net.LinkProperties#getNat64Prefix`"
-        errorLine1="        return lp.getNat64Prefix();"
-        errorLine2="                  ~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java"
-            line="85"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.LinkProperties#setNat64Prefix`"
-        errorLine1="        lp.setNat64Prefix(prefix);"
-        errorLine2="           ~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java"
-            line="90"
-            column="12"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.LinkProperties#setDhcpServerAddress`"
-        errorLine1="        lp.setDhcpServerAddress(serverAddress);"
-        errorLine2="           ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java"
-            line="109"
-            column="12"/>
-    </issue>
-
-</issues>
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 6746680..12ab3fd 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -906,6 +906,7 @@
 
     private void stopStateMachineUpdaters() {
         mObserverRegistry.unregisterObserver(mLinkObserver);
+        mLinkObserver.clearInterfaceParams();
         mLinkObserver.shutdown();
     }
 
@@ -1969,7 +1970,6 @@
             mHasDisabledIpv6OrAcceptRaOnProvLoss = false;
             mGratuitousNaTargetAddresses.clear();
 
-            mLinkObserver.clearInterfaceParams();
             resetLinkProperties();
             if (mStartTimeMillis > 0) {
                 // Completed a life-cycle; send a final empty LinkProperties
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index 36b6492..948ce8d 100755
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -16,6 +16,7 @@
 
 package com.android.server.connectivity;
 
+import static android.content.Intent.ACTION_CONFIGURATION_CHANGED;
 import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
 import static android.net.CaptivePortal.APP_RETURN_UNWANTED;
 import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
@@ -392,6 +393,11 @@
      */
     private static final int CMD_BANDWIDTH_CHECK_TIMEOUT = 24;
 
+    /**
+     * Message to self to notify resource configuration is changed.
+     */
+    private static final int EVENT_RESOURCE_CONFIG_CHANGED = 25;
+
     // Start mReevaluateDelayMs at this value and double.
     @VisibleForTesting
     static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
@@ -416,7 +422,6 @@
     private String mPrivateDnsProviderHostname = "";
 
     private final Context mContext;
-    private final Context mCustomizedContext;
     private final INetworkMonitorCallbacks mCallback;
     private final int mCallbackVersion;
     private final Network mCleartextDnsNetwork;
@@ -431,13 +436,21 @@
     private final TcpSocketTracker mTcpTracker;
     // Configuration values for captive portal detection probes.
     private final String mCaptivePortalUserAgent;
-    private final URL[] mCaptivePortalFallbackUrls;
-    @NonNull
-    private final URL[] mCaptivePortalHttpUrls;
-    @NonNull
-    private final URL[] mCaptivePortalHttpsUrls;
+    // Configuration values in setting providers for captive portal detection probes
+    private final String mCaptivePortalHttpsUrlFromSetting;
+    private final String mCaptivePortalHttpUrlFromSetting;
     @Nullable
     private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs;
+
+    // The probing URLs may be updated after constructor if system notifies configuration changed.
+    // Thus, these probing URLs should only be accessed in the StateMachine thread.
+    @NonNull
+    private URL[] mCaptivePortalFallbackUrls;
+    @NonNull
+    private URL[] mCaptivePortalHttpUrls;
+    @NonNull
+    private URL[] mCaptivePortalHttpsUrls;
+
     // Configuration values for network bandwidth check.
     @Nullable
     private final String mEvaluatingBandwidthUrl;
@@ -511,7 +524,8 @@
     private @EvaluationType int mDataStallTypeToCollect;
     private boolean mAcceptPartialConnectivity = false;
     private final EvaluationState mEvaluationState = new EvaluationState();
-
+    @NonNull
+    private final BroadcastReceiver mConfigurationReceiver;
     private final boolean mPrivateIpNoInternetEnabled;
 
     private final boolean mMetricsEnabled;
@@ -575,7 +589,6 @@
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
         mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
         mNotifier = serviceManager.getNotifier();
-        mCustomizedContext = getCustomizedContextOrDefault();
 
         // CHECKSTYLE:OFF IndentationCheck
         addState(mDefaultState);
@@ -590,16 +603,18 @@
         setInitialState(mDefaultState);
         // CHECKSTYLE:ON IndentationCheck
 
+        mCaptivePortalHttpsUrlFromSetting =
+                mDependencies.getSetting(context, CAPTIVE_PORTAL_HTTPS_URL, null);
+        mCaptivePortalHttpUrlFromSetting =
+                mDependencies.getSetting(context, CAPTIVE_PORTAL_HTTP_URL, null);
         mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
         mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled();
         mMetricsEnabled = deps.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY,
                 NetworkStackUtils.VALIDATION_METRICS_VERSION, true /* defaultEnabled */);
         mUseHttps = getUseHttpsValidation();
         mCaptivePortalUserAgent = getCaptivePortalUserAgent();
-        mCaptivePortalHttpsUrls = makeCaptivePortalHttpsUrls();
-        mCaptivePortalHttpUrls = makeCaptivePortalHttpUrls();
-        mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
-        mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs();
+        mCaptivePortalFallbackSpecs =
+                makeCaptivePortalFallbackProbeSpecs(getCustomizedContextOrDefault());
         mRandom = deps.getRandom();
         // TODO: Evaluate to move data stall configuration to a specific class.
         mConsecutiveDnsTimeoutThreshold = getConsecutiveDnsTimeoutThreshold();
@@ -618,15 +633,18 @@
         mEvaluatingBandwidthTimeoutMs = getResIntConfig(mContext,
                 R.integer.config_evaluating_bandwidth_timeout_ms,
                 DEFAULT_EVALUATING_BANDWIDTH_TIMEOUT_MS);
-
+        mConfigurationReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) {
+                    sendMessage(EVENT_RESOURCE_CONFIG_CHANGED);
+                }
+            }
+        };
         // Provide empty LinkProperties and NetworkCapabilities to make sure they are never null,
         // even before notifyNetworkConnected.
         mLinkProperties = new LinkProperties();
         mNetworkCapabilities = new NetworkCapabilities(null);
-
-        Log.d(TAG, "Starting on network " + mNetwork
-                + " with capport HTTPS URL " + Arrays.toString(mCaptivePortalHttpsUrls)
-                + " and HTTP URL " + Arrays.toString(mCaptivePortalHttpUrls));
     }
 
     /**
@@ -873,6 +891,18 @@
     // does not entail any real state (hence no enter() or exit() routines).
     private class DefaultState extends State {
         @Override
+        public void enter() {
+            // Register configuration broadcast here instead of constructor to prevent start() was
+            // not called yet when the broadcast is received and cause crash.
+            mContext.registerReceiver(mConfigurationReceiver,
+                    new IntentFilter(ACTION_CONFIGURATION_CHANGED));
+            checkAndRenewResourceConfig();
+            Log.d(TAG, "Starting on network " + mNetwork
+                    + " with capport HTTPS URL " + Arrays.toString(mCaptivePortalHttpsUrls)
+                    + " and HTTP URL " + Arrays.toString(mCaptivePortalHttpUrls));
+        }
+
+        @Override
         public boolean processMessage(Message message) {
             switch (message.what) {
                 case CMD_NETWORK_CONNECTED:
@@ -1018,11 +1048,27 @@
                     mNetworkCapabilities = (NetworkCapabilities) message.obj;
                     suppressNotificationIfNetworkRestricted();
                     break;
+                case EVENT_RESOURCE_CONFIG_CHANGED:
+                    // RRO generation does not happen during package installation and instead after
+                    // the OMS receives the PACKAGE_ADDED event, there is a delay where the old
+                    // idmap is used with the new target package resulting in the incorrect overlay
+                    // is used. Renew the resource if a configuration change is received.
+                    // TODO: Remove it once design to generate the idmaps during package
+                    //  installation in overlay manager and package manager is ready.
+                    if (checkAndRenewResourceConfig()) {
+                        sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 1 /* forceAccept */);
+                    }
+                    break;
                 default:
                     break;
             }
             return HANDLED;
         }
+
+        @Override
+        public void exit() {
+            mContext.unregisterReceiver(mConfigurationReceiver);
+        }
     }
 
     // Being in the ValidatedState State indicates a Network is:
@@ -1343,7 +1389,9 @@
                     return HANDLED;
                 case CMD_FORCE_REEVALUATION:
                     // The evaluation process restarts via EvaluatingState#enter.
-                    return shouldAcceptForceRevalidation() ? NOT_HANDLED : HANDLED;
+                    final boolean forceAccept = (message.arg2 != 0);
+                    return forceAccept || shouldAcceptForceRevalidation()
+                            ? NOT_HANDLED : HANDLED;
                 // Disable HTTPS probe and transition to EvaluatingPrivateDnsState because:
                 // 1. Network is connected and finish the network validation.
                 // 2. NetworkMonitor detects network is partial connectivity and user accepts it.
@@ -1595,8 +1643,13 @@
 
             final int token = ++mProbeToken;
             final ValidationProperties deps = new ValidationProperties(mNetworkCapabilities);
+            final URL fallbackUrl = nextFallbackUrl();
+            final URL[] httpsUrls = Arrays.copyOf(
+                    mCaptivePortalHttpsUrls, mCaptivePortalHttpsUrls.length);
+            final URL[] httpUrls = Arrays.copyOf(
+                    mCaptivePortalHttpUrls, mCaptivePortalHttpUrls.length);
             mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0,
-                    isCaptivePortal(deps))));
+                    isCaptivePortal(deps, httpsUrls, httpUrls, fallbackUrl))));
             mThread.start();
         }
 
@@ -1986,18 +2039,24 @@
         }
 
         final long now = System.currentTimeMillis();
-        if (expTime < now || (expTime - now) > TEST_URL_EXPIRATION_MS) return null;
+        if (expTime < now || (expTime - now) > TEST_URL_EXPIRATION_MS) {
+            logw("Skipping test URL with expiration " + expTime + ", now " + now);
+            return null;
+        }
 
         final String strUrl = mDependencies.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY,
                 key, null /* defaultValue */);
-        if (!isValidTestUrl(strUrl)) return null;
+        if (!isValidTestUrl(strUrl)) {
+            logw("Skipping invalid test URL " + strUrl);
+            return null;
+        }
         return makeURL(strUrl);
     }
 
-    private String getCaptivePortalServerHttpsUrl() {
-        return getSettingFromResource(mCustomizedContext,
-                R.string.config_captive_portal_https_url, CAPTIVE_PORTAL_HTTPS_URL,
-                mCustomizedContext.getResources().getString(
+    private String getCaptivePortalServerHttpsUrl(@NonNull Context context) {
+        return getSettingFromResource(context,
+                R.string.config_captive_portal_https_url, mCaptivePortalHttpsUrlFromSetting,
+                context.getResources().getString(
                 R.string.default_captive_portal_https_url));
     }
 
@@ -2074,10 +2133,10 @@
      * it has its own updatable strategies to detect captive portals. The framework only advises
      * on one URL that can be used, while NetworkMonitor may implement more complex logic.
      */
-    public String getCaptivePortalServerHttpUrl() {
-        return getSettingFromResource(mCustomizedContext,
-                R.string.config_captive_portal_http_url, CAPTIVE_PORTAL_HTTP_URL,
-                mCustomizedContext.getResources().getString(
+    public String getCaptivePortalServerHttpUrl(@NonNull Context context) {
+        return getSettingFromResource(context,
+                R.string.config_captive_portal_http_url, mCaptivePortalHttpUrlFromSetting,
+                context.getResources().getString(
                 R.string.default_captive_portal_http_url));
     }
 
@@ -2113,13 +2172,13 @@
     }
 
     @VisibleForTesting
-    URL[] makeCaptivePortalFallbackUrls() {
+    URL[] makeCaptivePortalFallbackUrls(@NonNull Context context) {
         try {
             final String firstUrl = mDependencies.getSetting(mContext, CAPTIVE_PORTAL_FALLBACK_URL,
                     null);
             final URL[] settingProviderUrls =
                 combineCaptivePortalUrls(firstUrl, CAPTIVE_PORTAL_OTHER_FALLBACK_URLS);
-            return getProbeUrlArrayConfig(settingProviderUrls,
+            return getProbeUrlArrayConfig(context, settingProviderUrls,
                     R.array.config_captive_portal_fallback_urls,
                     R.array.default_captive_portal_fallback_urls,
                     this::makeURL);
@@ -2130,7 +2189,7 @@
         }
     }
 
-    private CaptivePortalProbeSpec[] makeCaptivePortalFallbackProbeSpecs() {
+    private CaptivePortalProbeSpec[] makeCaptivePortalFallbackProbeSpecs(@NonNull Context context) {
         try {
             final String settingsValue = mDependencies.getDeviceConfigProperty(
                     NAMESPACE_CONNECTIVITY, CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS, null);
@@ -2140,7 +2199,7 @@
                     ? emptySpecs
                     : parseCaptivePortalProbeSpecs(settingsValue).toArray(emptySpecs);
 
-            return getProbeUrlArrayConfig(providerValue,
+            return getProbeUrlArrayConfig(context, providerValue,
                     R.array.config_captive_portal_fallback_probe_specs,
                     DEFAULT_CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS,
                     CaptivePortalProbeSpec::parseSpecOrNull);
@@ -2151,17 +2210,17 @@
         }
     }
 
-    private URL[] makeCaptivePortalHttpsUrls() {
+    private URL[] makeCaptivePortalHttpsUrls(@NonNull Context context) {
         final URL testUrl = getTestUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL);
         if (testUrl != null) return new URL[] { testUrl };
 
-        final String firstUrl = getCaptivePortalServerHttpsUrl();
+        final String firstUrl = getCaptivePortalServerHttpsUrl(context);
         try {
             final URL[] settingProviderUrls =
                 combineCaptivePortalUrls(firstUrl, CAPTIVE_PORTAL_OTHER_HTTPS_URLS);
             // firstUrl will at least be default configuration, so default value in
             // getProbeUrlArrayConfig is actually never used.
-            return getProbeUrlArrayConfig(settingProviderUrls,
+            return getProbeUrlArrayConfig(context, settingProviderUrls,
                     R.array.config_captive_portal_https_urls,
                     DEFAULT_CAPTIVE_PORTAL_HTTPS_URLS, this::makeURL);
         } catch (Exception e) {
@@ -2172,17 +2231,17 @@
         }
     }
 
-    private URL[] makeCaptivePortalHttpUrls() {
+    private URL[] makeCaptivePortalHttpUrls(@NonNull Context context) {
         final URL testUrl = getTestUrl(TEST_CAPTIVE_PORTAL_HTTP_URL);
         if (testUrl != null) return new URL[] { testUrl };
 
-        final String firstUrl = getCaptivePortalServerHttpUrl();
+        final String firstUrl = getCaptivePortalServerHttpUrl(context);
         try {
             final URL[] settingProviderUrls =
                     combineCaptivePortalUrls(firstUrl, CAPTIVE_PORTAL_OTHER_HTTP_URLS);
             // firstUrl will at least be default configuration, so default value in
             // getProbeUrlArrayConfig is actually never used.
-            return getProbeUrlArrayConfig(settingProviderUrls,
+            return getProbeUrlArrayConfig(context, settingProviderUrls,
                     R.array.config_captive_portal_http_urls,
                     DEFAULT_CAPTIVE_PORTAL_HTTP_URLS, this::makeURL);
         } catch (Exception e) {
@@ -2210,21 +2269,20 @@
      * <p>The configuration resource is prioritized, then the provider value.
      * @param context The context
      * @param configResource The resource id for the configuration parameter
-     * @param symbol The symbol in the settings provider
+     * @param settingValue The value in the settings provider
      * @param defaultValue The default value
      * @return The best available value
      */
     @Nullable
     private String getSettingFromResource(@NonNull final Context context,
-            @StringRes int configResource, @NonNull String symbol, @NonNull String defaultValue) {
+            @StringRes int configResource, @NonNull String settingValue,
+            @NonNull String defaultValue) {
         final Resources res = context.getResources();
         String setting = res.getString(configResource);
 
         if (!TextUtils.isEmpty(setting)) return setting;
 
-        setting = mDependencies.getSetting(context, symbol, null);
-
-        if (!TextUtils.isEmpty(setting)) return setting;
+        if (!TextUtils.isEmpty(settingValue)) return settingValue;
 
         return defaultValue;
     }
@@ -2234,17 +2292,20 @@
      *
      * <p>The configuration resource is prioritized, then the provider values, then the default
      * resource values.
+     *
+     * @param context The Context
      * @param providerValue Values obtained from the setting provider.
      * @param configResId ID of the configuration resource.
      * @param defaultResId ID of the default resource.
      * @param resourceConverter Converter from the resource strings to stored setting class. Null
      *                          return values are ignored.
      */
-    private <T> T[] getProbeUrlArrayConfig(@NonNull T[] providerValue, @ArrayRes int configResId,
-            @ArrayRes int defaultResId, @NonNull Function<String, T> resourceConverter) {
-        final Resources res = mCustomizedContext.getResources();
-        return getProbeUrlArrayConfig(providerValue, configResId, res.getStringArray(defaultResId),
-                resourceConverter);
+    private <T> T[] getProbeUrlArrayConfig(@NonNull Context context, @NonNull T[] providerValue,
+            @ArrayRes int configResId, @ArrayRes int defaultResId,
+            @NonNull Function<String, T> resourceConverter) {
+        final Resources res = context.getResources();
+        return getProbeUrlArrayConfig(context, providerValue, configResId,
+                res.getStringArray(defaultResId), resourceConverter);
     }
 
     /**
@@ -2252,15 +2313,18 @@
      *
      * <p>The configuration resource is prioritized, then the provider values, then the default
      * resource values.
+     *
+     * @param context The Context
      * @param providerValue Values obtained from the setting provider.
      * @param configResId ID of the configuration resource.
      * @param defaultConfig Values of default configuration.
      * @param resourceConverter Converter from the resource strings to stored setting class. Null
      *                          return values are ignored.
      */
-    private <T> T[] getProbeUrlArrayConfig(@NonNull T[] providerValue, @ArrayRes int configResId,
-            String[] defaultConfig, @NonNull Function<String, T> resourceConverter) {
-        final Resources res = mCustomizedContext.getResources();
+    private <T> T[] getProbeUrlArrayConfig(@NonNull Context context, @NonNull T[] providerValue,
+            @ArrayRes int configResId, String[] defaultConfig,
+            @NonNull Function<String, T> resourceConverter) {
+        final Resources res = context.getResources();
         String[] configValue = res.getStringArray(configResId);
 
         if (configValue.length == 0) {
@@ -2339,15 +2403,14 @@
         }
     }
 
-    private CaptivePortalProbeResult isCaptivePortal(ValidationProperties properties) {
+    private CaptivePortalProbeResult isCaptivePortal(ValidationProperties properties,
+            URL[] httpsUrls, URL[] httpUrls, URL fallbackUrl) {
         if (!mIsCaptivePortalCheckEnabled) {
             validationLog("Validation disabled.");
             return CaptivePortalProbeResult.success(CaptivePortalProbeResult.PROBE_UNKNOWN);
         }
 
         URL pacUrl = null;
-        final URL[] httpsUrls = mCaptivePortalHttpsUrls;
-        final URL[] httpUrls = mCaptivePortalHttpUrls;
 
         // On networks with a PAC instead of fetching a URL that should result in a 204
         // response, we instead simply fetch the PAC script.  This is done for a few reasons:
@@ -2388,7 +2451,7 @@
         } else if (mUseHttps && httpsUrls.length == 1 && httpUrls.length == 1) {
             // Probe results are reported inside sendHttpAndHttpsParallelWithFallbackProbes.
             result = sendHttpAndHttpsParallelWithFallbackProbes(properties, proxyInfo,
-                    httpsUrls[0], httpUrls[0]);
+                    httpsUrls[0], httpUrls[0], fallbackUrl);
         } else if (mUseHttps) {
             // Support result aggregation from multiple Urls.
             result = sendMultiParallelHttpAndHttpsProbes(properties, proxyInfo, httpsUrls,
@@ -2990,7 +3053,8 @@
     }
 
     private CaptivePortalProbeResult sendHttpAndHttpsParallelWithFallbackProbes(
-            ValidationProperties properties, ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
+            ValidationProperties properties, ProxyInfo proxy, URL httpsUrl, URL httpUrl,
+            URL fallbackUrl) {
         // Number of probes to wait for. If a probe completes with a conclusive answer
         // it shortcuts the latch immediately by forcing the count to 0.
         final CountDownLatch latch = new CountDownLatch(2);
@@ -3036,10 +3100,10 @@
         // If a fallback method exists, use it to retry portal detection.
         // If we have new-style probe specs, use those. Otherwise, use the fallback URLs.
         final CaptivePortalProbeSpec probeSpec = nextFallbackSpec();
-        final URL fallbackUrl = (probeSpec != null) ? probeSpec.getUrl() : nextFallbackUrl();
+        final URL fallback = (probeSpec != null) ? probeSpec.getUrl() : fallbackUrl;
         CaptivePortalProbeResult fallbackProbeResult = null;
-        if (fallbackUrl != null) {
-            fallbackProbeResult = sendHttpProbe(fallbackUrl, PROBE_FALLBACK, probeSpec);
+        if (fallback != null) {
+            fallbackProbeResult = sendHttpProbe(fallback, PROBE_FALLBACK, probeSpec);
             reportHttpProbeResult(NETWORK_VALIDATION_PROBE_FALLBACK, fallbackProbeResult);
             if (fallbackProbeResult.isPortal()) {
                 return fallbackProbeResult;
@@ -3610,4 +3674,37 @@
                 && captivePortalDataShim.getUserPortalUrlSource()
                 == ConstantsShim.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT;
     }
+
+    private boolean checkAndRenewResourceConfig() {
+        boolean reevaluationNeeded = false;
+
+        final Context customizedContext = getCustomizedContextOrDefault();
+        final URL[] captivePortalHttpsUrls = makeCaptivePortalHttpsUrls(customizedContext);
+        if (!Arrays.equals(mCaptivePortalHttpsUrls, captivePortalHttpsUrls)) {
+            mCaptivePortalHttpsUrls = captivePortalHttpsUrls;
+            reevaluationNeeded = true;
+            log("checkAndRenewResourceConfig: update captive portal https urls to "
+                    + Arrays.toString(mCaptivePortalHttpsUrls));
+        }
+
+        final URL[] captivePortalHttpUrls = makeCaptivePortalHttpUrls(customizedContext);
+        if (!Arrays.equals(mCaptivePortalHttpUrls, captivePortalHttpUrls)) {
+            mCaptivePortalHttpUrls = captivePortalHttpUrls;
+            reevaluationNeeded = true;
+            log("checkAndRenewResourceConfig: update captive portal http urls to "
+                    + Arrays.toString(mCaptivePortalHttpUrls));
+        }
+
+        final URL[] captivePortalFallbackUrls = makeCaptivePortalFallbackUrls(customizedContext);
+        if (!Arrays.equals(mCaptivePortalFallbackUrls, captivePortalFallbackUrls)) {
+            mCaptivePortalFallbackUrls = captivePortalFallbackUrls;
+            // Reset the index since the array is changed.
+            mNextFallbackUrlIndex = 0;
+            reevaluationNeeded = true;
+            log("checkAndRenewResourceConfig: update captive portal fallback urls to"
+                    + Arrays.toString(mCaptivePortalFallbackUrls));
+        }
+
+        return reevaluationNeeded;
+    }
 }
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
index bbbe0ba..6ade54f 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
@@ -146,7 +146,6 @@
 import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.HexDump;
 import com.android.internal.util.StateMachine;
@@ -181,6 +180,7 @@
 import org.junit.Test;
 import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
@@ -224,7 +224,7 @@
  *
  * Tests in this class can either be run with signature permissions, or with root access.
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 @SmallTest
 public abstract class IpClientIntegrationTestCommon {
     private static final int DATA_BUFFER_LEN = 4096;
@@ -247,6 +247,17 @@
     @Rule
     public final TestName mTestNameRule = new TestName();
 
+    // Indicate whether the flag of parsing netlink event is enabled or not. If it's disabled,
+    // integration test still covers the old codepath(i.e. using NetworkObserver), otherwise,
+    // test goes through the new codepath(i.e. processRtNetlinkxxx).
+    @Parameterized.Parameter(0)
+    public boolean mIsNetlinkEventParseEnabled;
+
+    @Parameterized.Parameters
+    public static Iterable<? extends Object> data() {
+        return Arrays.asList(Boolean.valueOf("false"), Boolean.valueOf("true"));
+    }
+
     /**
      * Indicates that a test requires signature permissions to run.
      *
@@ -553,8 +564,14 @@
 
     @Before
     public void setUp() throws Exception {
-        final Method testMethod = IpClientIntegrationTestCommon.class.getMethod(
-                mTestNameRule.getMethodName());
+        // Suffix "[0]" or "[1]" is added to the end of test method name after running with
+        // Parameterized.class, that's intended behavior, to iterate each test method with the
+        // parameterize value. However, Class#getMethod() throws NoSuchMethodException when
+        // searching the target test method name due to this change. Just keep the original test
+        // method name to fix NoSuchMethodException, and find the correct annotation associated
+        // to test method.
+        final String testMethodName = mTestNameRule.getMethodName().split("\\[")[0];
+        final Method testMethod = IpClientIntegrationTestCommon.class.getMethod(testMethodName);
         mIsSignatureRequiredTest = testMethod.getAnnotation(SignatureRequiredTest.class) != null;
         assumeFalse(testSkipped());
 
@@ -567,6 +584,12 @@
         }
 
         mIIpClient = makeIIpClient(mIfaceName, mCb);
+
+        // Depend on the parameterized value to enable/disable netlink message refactor flag.
+        // Make sure both of the old codepath(rely on the INetdUnsolicitedEventListener aidl)
+        // and new codepath(parse netlink event from kernel) will be executed.
+        setFeatureEnabled(NetworkStackUtils.IPCLIENT_PARSE_NETLINK_EVENTS_VERSION,
+                mIsNetlinkEventParseEnabled /* default value */);
     }
 
     protected void setUpMocks() throws Exception {
diff --git a/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt b/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
index 3b44597..9fda189 100644
--- a/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
+++ b/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
@@ -50,6 +50,8 @@
 import com.android.networkstack.NetworkStackNotifier.CONNECTED_NOTIFICATION_TIMEOUT_MS
 import com.android.networkstack.NetworkStackNotifier.Dependencies
 import com.android.networkstack.apishim.NetworkInformationShimImpl
+import com.android.modules.utils.build.SdkLevel.isAtLeastR
+import com.android.modules.utils.build.SdkLevel.isAtLeastS
 import org.junit.Assume.assumeTrue
 import org.junit.Before
 import org.junit.Test
@@ -235,7 +237,7 @@
     @Test
     fun testConnectedNotification_WithSsid() {
         // NetworkCapabilities#getSSID is not available for API <= Q
-        assumeTrue(NetworkInformationShimImpl.useApiAboveQ())
+        assumeTrue(isAtLeastR())
         val capabilities = NetworkCapabilities(VALIDATED_CAPABILITIES).setSSID(TEST_SSID)
 
         onCapabilitiesChanged(EMPTY_CAPABILITIES)
@@ -252,7 +254,7 @@
     @Test
     fun testConnectedVenueInfoNotification() {
         // Venue info (CaptivePortalData) is not available for API <= Q
-        assumeTrue(NetworkInformationShimImpl.useApiAboveQ())
+        assumeTrue(isAtLeastR())
         mNotifier.notifyCaptivePortalValidationPending(TEST_NETWORK)
         onLinkPropertiesChanged(mTestCapportLp)
         onDefaultNetworkAvailable(TEST_NETWORK)
@@ -270,7 +272,7 @@
     @Test
     fun testConnectedVenueInfoNotification_VenueInfoDisabled() {
         // Venue info (CaptivePortalData) is not available for API <= Q
-        assumeTrue(NetworkInformationShimImpl.useApiAboveQ())
+        assumeTrue(isAtLeastR())
         val channel = NotificationChannel(CHANNEL_VENUE_INFO, "test channel", IMPORTANCE_NONE)
         doReturn(channel).`when`(mNotificationChannelsNm).getNotificationChannel(CHANNEL_VENUE_INFO)
         mNotifier.notifyCaptivePortalValidationPending(TEST_NETWORK)
@@ -289,7 +291,7 @@
     @Test
     fun testVenueInfoNotification() {
         // Venue info (CaptivePortalData) is not available for API <= Q
-        assumeTrue(NetworkInformationShimImpl.useApiAboveQ())
+        assumeTrue(isAtLeastR())
         onLinkPropertiesChanged(mTestCapportLp)
         onDefaultNetworkAvailable(TEST_NETWORK)
         val capabilities = NetworkCapabilities(VALIDATED_CAPABILITIES).setSSID(TEST_SSID)
@@ -307,7 +309,7 @@
     @Test
     fun testVenueInfoNotification_VenueInfoDisabled() {
         // Venue info (CaptivePortalData) is not available for API <= Q
-        assumeTrue(NetworkInformationShimImpl.useApiAboveQ())
+        assumeTrue(isAtLeastR())
         doReturn(null).`when`(mNm).getNotificationChannel(CHANNEL_VENUE_INFO)
         onLinkPropertiesChanged(mTestCapportLp)
         onDefaultNetworkAvailable(TEST_NETWORK)
@@ -320,7 +322,7 @@
     @Test
     fun testNonDefaultVenueInfoNotification() {
         // Venue info (CaptivePortalData) is not available for API <= Q
-        assumeTrue(NetworkInformationShimImpl.useApiAboveQ())
+        assumeTrue(isAtLeastR())
         onLinkPropertiesChanged(mTestCapportLp)
         onCapabilitiesChanged(VALIDATED_CAPABILITIES)
         mLooper.processAllMessages()
@@ -331,7 +333,7 @@
     @Test
     fun testEmptyCaptivePortalDataVenueInfoNotification() {
         // Venue info (CaptivePortalData) is not available for API <= Q
-        assumeTrue(NetworkInformationShimImpl.useApiAboveQ())
+        assumeTrue(isAtLeastR())
         onLinkPropertiesChanged(EMPTY_CAPPORT_LP)
         onCapabilitiesChanged(VALIDATED_CAPABILITIES)
         mLooper.processAllMessages()
@@ -342,7 +344,7 @@
     @Test
     fun testUnvalidatedNetworkVenueInfoNotification() {
         // Venue info (CaptivePortalData) is not available for API <= Q
-        assumeTrue(NetworkInformationShimImpl.useApiAboveQ())
+        assumeTrue(isAtLeastR())
         onLinkPropertiesChanged(mTestCapportLp)
         onCapabilitiesChanged(EMPTY_CAPABILITIES)
         mLooper.processAllMessages()
@@ -353,7 +355,7 @@
     @Test
     fun testConnectedVenueInfoWithFriendlyNameNotification() {
         // Venue info (CaptivePortalData) with friendly name is not available for API <= R
-        assumeTrue(NetworkInformationShimImpl.useApiAboveR())
+        assumeTrue(isAtLeastS())
         mNotifier.notifyCaptivePortalValidationPending(TEST_NETWORK)
         onLinkPropertiesChanged(mTestCapportVenueUrlWithFriendlyNameLp)
         onDefaultNetworkAvailable(TEST_NETWORK)
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index 9d392eb..ff43325 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.connectivity;
 
+import static android.content.Intent.ACTION_CONFIGURATION_CHANGED;
 import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
 import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
 import static android.net.DnsResolver.TYPE_A;
@@ -637,6 +638,7 @@
 
         @Override
         protected void onQuitting() {
+            super.onQuitting();
             assertTrue(mCreatedNetworkMonitors.remove(this));
             mQuitCv.open();
         }
@@ -863,7 +865,6 @@
 
     @Test
     public void testGetHttpProbeUrl() {
-        final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
         // If config_captive_portal_http_url is set and the global setting is set, the config is
         // used.
         doReturn(TEST_HTTP_URL).when(mResources).getString(R.string.config_captive_portal_http_url);
@@ -871,16 +872,21 @@
                 R.string.default_captive_portal_http_url);
         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL), any()))
                 .thenReturn(TEST_HTTP_OTHER_URL1);
-        assertEquals(TEST_HTTP_URL, wnm.getCaptivePortalServerHttpUrl());
+        final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
+        assertEquals(TEST_HTTP_URL, wnm.getCaptivePortalServerHttpUrl(mContext));
         // If config_captive_portal_http_url is unset and the global setting is set, the global
         // setting is used.
         doReturn(null).when(mResources).getString(R.string.config_captive_portal_http_url);
-        assertEquals(TEST_HTTP_OTHER_URL1, wnm.getCaptivePortalServerHttpUrl());
+        assertEquals(TEST_HTTP_OTHER_URL1, wnm.getCaptivePortalServerHttpUrl(mContext));
         // If both config_captive_portal_http_url and global setting are unset,
-        // default_captive_portal_http_url is used.
+        // default_captive_portal_http_url is used. But the global setting will only be read in the
+        // constructor.
         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL), any()))
                 .thenReturn(null);
-        assertEquals(TEST_HTTP_OTHER_URL2, wnm.getCaptivePortalServerHttpUrl());
+        assertEquals(TEST_HTTP_OTHER_URL1, wnm.getCaptivePortalServerHttpUrl(mContext));
+        // default_captive_portal_http_url is used when the configuration is applied in new NM.
+        final WrappedNetworkMonitor wnm2 = makeCellNotMeteredNetworkMonitor();
+        assertEquals(TEST_HTTP_OTHER_URL2, wnm2.getCaptivePortalServerHttpUrl(mContext));
     }
 
     @Test
@@ -937,6 +943,57 @@
         }
     }
 
+    @Test
+    public void testConfigurationChange_BeforeNMConnected() throws Exception {
+        final WrappedNetworkMonitor nm = new WrappedNetworkMonitor();
+        final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+        // Verify configuration change receiver is registered after start().
+        verify(mContext, never()).registerReceiver(receiverCaptor.capture(),
+                argThat(receiver -> ACTION_CONFIGURATION_CHANGED.equals(receiver.getAction(0))));
+        nm.start();
+        mCreatedNetworkMonitors.add(nm);
+        HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
+        verify(mContext, times(1)).registerReceiver(receiverCaptor.capture(),
+                argThat(receiver -> ACTION_CONFIGURATION_CHANGED.equals(receiver.getAction(0))));
+        // Update a new URL and send a configuration change
+        doReturn(TEST_HTTPS_OTHER_URL1).when(mResources).getString(
+                R.string.config_captive_portal_https_url);
+        receiverCaptor.getValue().onReceive(mContext, new Intent(ACTION_CONFIGURATION_CHANGED));
+        HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
+        // Should stay in default state before receiving CMD_NETWORK_CONNECTED
+        verify(mOtherHttpsConnection1, never()).getResponseCode();
+    }
+
+    @Test
+    public void testIsCaptivePortal_ConfigurationChange_RenewUrls() throws Exception {
+        setStatus(mHttpsConnection, 204);
+        final NetworkMonitor nm = runValidatedNetworkTest();
+        final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext, times(1)).registerReceiver(receiverCaptor.capture(),
+                argThat(receiver -> ACTION_CONFIGURATION_CHANGED.equals(receiver.getAction(0))));
+
+        resetCallbacks();
+        // New URLs with partial connectivity
+        doReturn(TEST_HTTPS_OTHER_URL1).when(mResources).getString(
+                R.string.config_captive_portal_https_url);
+        doReturn(TEST_HTTP_OTHER_URL1).when(mResources).getString(
+                R.string.config_captive_portal_http_url);
+        setStatus(mOtherHttpsConnection1, 500);
+        setStatus(mOtherHttpConnection1, 204);
+
+        // Receive configuration. Expect a reevaluation triggered.
+        receiverCaptor.getValue().onReceive(mContext, new Intent(ACTION_CONFIGURATION_CHANGED));
+
+        HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
+        verifyNetworkTested(NETWORK_VALIDATION_RESULT_PARTIAL,
+                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP);
+        verify(mOtherHttpsConnection1, times(1)).getResponseCode();
+        verify(mOtherHttpConnection1, times(1)).getResponseCode();
+    }
+
     private CellInfoGsm makeTestCellInfoGsm(String mcc) throws Exception {
         final CellInfoGsm info = new CellInfoGsm();
         final CellIdentityGsm ci = makeCellIdentityGsm(0, 0, 0, 0, mcc, "01", "", "");
@@ -967,7 +1024,7 @@
     public void testMakeFallbackUrls() throws Exception {
         final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
         // Value exist in setting provider.
-        URL[] urls = wnm.makeCaptivePortalFallbackUrls();
+        URL[] urls = wnm.makeCaptivePortalFallbackUrls(mContext);
         assertEquals(urls[0].toString(), TEST_FALLBACK_URL);
 
         // Clear setting provider value. Verify it to get configuration from resource instead.
@@ -975,13 +1032,13 @@
         // Verify that getting resource with exception.
         when(mResources.getStringArray(R.array.config_captive_portal_fallback_urls))
                 .thenThrow(Resources.NotFoundException.class);
-        urls = wnm.makeCaptivePortalFallbackUrls();
+        urls = wnm.makeCaptivePortalFallbackUrls(mContext);
         assertEquals(urls.length, 0);
 
         // Verify resource return 2 different URLs.
         doReturn(new String[] {"http://testUrl1.com", "http://testUrl2.com"}).when(mResources)
                 .getStringArray(R.array.config_captive_portal_fallback_urls);
-        urls = wnm.makeCaptivePortalFallbackUrls();
+        urls = wnm.makeCaptivePortalFallbackUrls(mContext);
         assertEquals(urls.length, 2);
         assertEquals("http://testUrl1.com", urls[0].toString());
         assertEquals("http://testUrl2.com", urls[1].toString());
@@ -992,7 +1049,7 @@
         setupNoSimCardNeighborMcc();
         doReturn(new String[] {"http://testUrl3.com"}).when(mMccResource)
                 .getStringArray(R.array.config_captive_portal_fallback_urls);
-        urls = wnm.makeCaptivePortalFallbackUrls();
+        urls = wnm.makeCaptivePortalFallbackUrls(mContext);
         assertEquals(urls.length, 2);
         assertEquals("http://testUrl1.com", urls[0].toString());
         assertEquals("http://testUrl2.com", urls[1].toString());
@@ -1005,7 +1062,7 @@
         doReturn(new String[] {"http://testUrl.com"}).when(mMccResource)
                 .getStringArray(R.array.config_captive_portal_fallback_urls);
         final WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
-        final URL[] urls = wnm.makeCaptivePortalFallbackUrls();
+        final URL[] urls = wnm.makeCaptivePortalFallbackUrls(mMccContext);
         assertEquals(urls.length, 1);
         assertEquals("http://testUrl.com", urls[0].toString());
     }
@@ -1894,7 +1951,7 @@
         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
                 .showProvisioningNotification(any(), any());
 
-        assertEquals(1, mRegisteredReceivers.size());
+        assertCaptivePortalAppReceiverRegistered(true /* isPortal */);
 
         // Check that startCaptivePortalApp sends the expected intent.
         nm.launchCaptivePortalApp();
@@ -1932,7 +1989,7 @@
                 .notifyNetworkTestedWithExtras(matchNetworkTestResultParcelable(
                         NETWORK_VALIDATION_RESULT_VALID,
                         NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP));
-        assertEquals(0, mRegisteredReceivers.size());
+        assertCaptivePortalAppReceiverRegistered(false /* isPortal */);
     }
 
     @Test
@@ -2518,7 +2575,7 @@
         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
             .showProvisioningNotification(any(), any());
 
-        assertEquals(1, mRegisteredReceivers.size());
+        assertCaptivePortalAppReceiverRegistered(true /* isPortal */);
         // Check that startCaptivePortalApp sends the expected intent.
         nm.launchCaptivePortalApp();
 
@@ -2740,7 +2797,7 @@
         monitor.notifyNetworkConnected(linkProperties, networkCapabilities);
         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
                 .showProvisioningNotification(any(), any());
-        assertEquals(1, mRegisteredReceivers.size());
+        assertCaptivePortalAppReceiverRegistered(true /* isPortal */);
         verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL);
 
         // Force reevaluation and confirm that the network is still captive
@@ -2903,21 +2960,21 @@
     private NetworkMonitor runPortalNetworkTest() throws RemoteException {
         final NetworkMonitor nm = runNetworkTest(VALIDATION_RESULT_PORTAL,
                 0 /* probesSucceeded */, TEST_LOGIN_URL);
-        assertEquals(1, mRegisteredReceivers.size());
+        assertCaptivePortalAppReceiverRegistered(true /* isPortal */);
         return nm;
     }
 
     private NetworkMonitor runNoValidationNetworkTest() throws RemoteException {
         final NetworkMonitor nm = runNetworkTest(NETWORK_VALIDATION_RESULT_VALID,
                 0 /* probesSucceeded */, null /* redirectUrl */);
-        assertEquals(0, mRegisteredReceivers.size());
+        assertCaptivePortalAppReceiverRegistered(false /* isPortal */);
         return nm;
     }
 
     private NetworkMonitor runFailedNetworkTest() throws RemoteException {
         final NetworkMonitor nm = runNetworkTest(
                 VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */, null /* redirectUrl */);
-        assertEquals(0, mRegisteredReceivers.size());
+        assertCaptivePortalAppReceiverRegistered(false /* isPortal */);
         return nm;
     }
 
@@ -2925,7 +2982,7 @@
             throws RemoteException {
         final NetworkMonitor nm = runNetworkTest(NETWORK_VALIDATION_RESULT_PARTIAL,
                 probesSucceeded, null /* redirectUrl */);
-        assertEquals(0, mRegisteredReceivers.size());
+        assertCaptivePortalAppReceiverRegistered(false /* isPortal */);
         return nm;
     }
 
@@ -3057,5 +3114,13 @@
     private DataStallReportParcelable matchTcpDataStallParcelable() {
         return argThat(p -> (p.detectionMethod & ConstantsShim.DETECTION_METHOD_TCP_METRICS) != 0);
     }
+
+    private void assertCaptivePortalAppReceiverRegistered(boolean isPortal) {
+        // There will be configuration change receiver registered after NetworkMonitor being
+        // started. If captive portal app receiver is registered, then the size of the registered
+        // receivers will be 2. Otherwise, mRegisteredReceivers should only contain 1 configuration
+        // change receiver.
+        assertEquals(isPortal ? 2 : 1, mRegisteredReceivers.size());
+    }
 }