Merge "base: Add AudioEffect Interface to wishlist for dumping" into main
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index ef1fa60..ec4fc46 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -239,6 +239,10 @@
     name: "android-non-updatable_from_source_defaults",
     libs: ["stub-annotations"],
     static_libs: ["framework-res-package-jar"], // Export package of framework-res
+}
+
+java_defaults {
+    name: "android-non-updatable_exportable_from_source_defaults",
     dist: {
         targets: ["sdk"],
         tag: ".jar",
@@ -265,6 +269,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.system",
     defaults: ["android-non-updatable_defaults"],
     static_libs: [
@@ -283,6 +295,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable.system",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.system.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.module_lib",
     defaults: ["android-non-updatable_defaults"],
     static_libs: [
@@ -301,6 +321,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable.module_lib",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.module_lib.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.test",
     defaults: ["android-non-updatable_defaults"],
     static_libs: [
@@ -319,6 +347,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable.test",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.test.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.from-source",
     defaults: [
         "android-non-updatable_defaults",
@@ -326,6 +362,17 @@
     ],
     srcs: [":api-stubs-docs-non-updatable"],
     libs: ["all-modules-public-stubs"],
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":api-stubs-docs-non-updatable{.exportable}"],
+    libs: ["all-modules-public-stubs"],
     dist: {
         dir: "apistubs/android/public",
     },
@@ -339,6 +386,17 @@
     ],
     srcs: [":system-api-stubs-docs-non-updatable"],
     libs: ["all-modules-system-stubs"],
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.system.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":system-api-stubs-docs-non-updatable{.exportable}"],
+    libs: ["all-modules-system-stubs"],
     dist: {
         dir: "apistubs/android/system",
     },
@@ -352,6 +410,17 @@
     ],
     srcs: [":module-lib-api-stubs-docs-non-updatable"],
     libs: non_updatable_api_deps_on_modules,
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.module_lib.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":module-lib-api-stubs-docs-non-updatable{.exportable}"],
+    libs: non_updatable_api_deps_on_modules,
     dist: {
         dir: "apistubs/android/module-lib",
     },
@@ -365,6 +434,17 @@
     ],
     srcs: [":test-api-stubs-docs-non-updatable"],
     libs: ["all-modules-system-stubs"],
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.test.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":test-api-stubs-docs-non-updatable{.exportable}"],
+    libs: ["all-modules-system-stubs"],
     dist: {
         dir: "apistubs/android/test",
     },
@@ -462,6 +542,16 @@
 }
 
 java_library {
+    name: "android_stubs_current_exportable.from-source",
+    static_libs: [
+        "all-modules-public-stubs-exportable",
+        "android-non-updatable.stubs.exportable",
+        "private-stub-annotations-jar",
+    ],
+    defaults: ["android.jar_defaults"],
+}
+
+java_library {
     name: "android_system_stubs_current.from-source",
     static_libs: [
         "all-modules-system-stubs",
@@ -470,6 +560,19 @@
     ],
     defaults: [
         "android.jar_defaults",
+    ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_system_stubs_current_exportable.from-source",
+    static_libs: [
+        "all-modules-system-stubs-exportable",
+        "android-non-updatable.stubs.exportable.system",
+        "private-stub-annotations-jar",
+    ],
+    defaults: [
+        "android.jar_defaults",
         "android_stubs_dists_default",
     ],
     dist: {
@@ -498,6 +601,23 @@
     ],
     defaults: [
         "android.jar_defaults",
+    ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_test_stubs_current_exportable.from-source",
+    static_libs: [
+        // Updatable modules do not have test APIs, but we want to include their SystemApis, like we
+        // include the SystemApi of framework-non-updatable-sources.
+        "all-updatable-modules-system-stubs-exportable",
+        // Non-updatable modules on the other hand can have test APIs, so include their test-stubs.
+        "all-non-updatable-modules-test-stubs-exportable",
+        "android-non-updatable.stubs.exportable.test",
+        "private-stub-annotations-jar",
+    ],
+    defaults: [
+        "android.jar_defaults",
         "android_stubs_dists_default",
     ],
     dist: {
@@ -505,6 +625,7 @@
     },
 }
 
+// This module does not need to be copied to dist
 java_library {
     name: "android_test_frameworks_core_stubs_current.from-source",
     static_libs: [
@@ -513,24 +634,34 @@
     ],
     defaults: [
         "android.jar_defaults",
-        "android_stubs_dists_default",
     ],
-    dist: {
-        dir: "apistubs/android/test-core",
-    },
+    visibility: ["//frameworks/base/services"],
 }
 
 java_library {
     name: "android_module_lib_stubs_current.from-source",
     defaults: [
         "android.jar_defaults",
-        "android_stubs_dists_default",
     ],
     static_libs: [
         "android-non-updatable.stubs.module_lib",
         "art.module.public.api.stubs.module_lib",
         "i18n.module.public.api.stubs",
     ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_module_lib_stubs_current_exportable.from-source",
+    defaults: [
+        "android.jar_defaults",
+        "android_stubs_dists_default",
+    ],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.module_lib",
+        "art.module.public.api.stubs.exportable.module_lib",
+        "i18n.module.public.api.stubs.exportable",
+    ],
     dist: {
         dir: "apistubs/android/module-lib",
     },
@@ -540,13 +671,26 @@
     name: "android_system_server_stubs_current.from-source",
     defaults: [
         "android.jar_defaults",
-        "android_stubs_dists_default",
     ],
     srcs: [":services-non-updatable-stubs"],
     installable: false,
     static_libs: [
         "android_module_lib_stubs_current.from-source",
     ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_system_server_stubs_current_exportable.from-source",
+    defaults: [
+        "android.jar_defaults",
+        "android_stubs_dists_default",
+    ],
+    srcs: [":services-non-updatable-stubs{.exportable}"],
+    installable: false,
+    static_libs: [
+        "android_module_lib_stubs_current_exportable.from-source",
+    ],
     dist: {
         dir: "apistubs/android/system-server",
     },
diff --git a/api/api.go b/api/api.go
index 43713aa..a632582 100644
--- a/api/api.go
+++ b/api/api.go
@@ -205,6 +205,15 @@
 	ctx.CreateModule(java.LibraryFactory, &props)
 }
 
+func createMergedPublicExportableStubs(ctx android.LoadHookContext, modules []string) {
+	props := libraryProps{}
+	props.Name = proptools.StringPtr("all-modules-public-stubs-exportable")
+	props.Static_libs = transformArray(modules, "", ".stubs.exportable")
+	props.Sdk_version = proptools.StringPtr("module_current")
+	props.Visibility = []string{"//frameworks/base"}
+	ctx.CreateModule(java.LibraryFactory, &props)
+}
+
 func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
 	// First create the all-updatable-modules-system-stubs
 	{
@@ -229,6 +238,30 @@
 	}
 }
 
+func createMergedSystemExportableStubs(ctx android.LoadHookContext, modules []string) {
+	// First create the all-updatable-modules-system-stubs
+	{
+		updatable_modules := removeAll(modules, non_updatable_modules)
+		props := libraryProps{}
+		props.Name = proptools.StringPtr("all-updatable-modules-system-stubs-exportable")
+		props.Static_libs = transformArray(updatable_modules, "", ".stubs.exportable.system")
+		props.Sdk_version = proptools.StringPtr("module_current")
+		props.Visibility = []string{"//frameworks/base"}
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+	// Now merge all-updatable-modules-system-stubs and stubs from non-updatable modules
+	// into all-modules-system-stubs.
+	{
+		props := libraryProps{}
+		props.Name = proptools.StringPtr("all-modules-system-stubs-exportable")
+		props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.exportable.system")
+		props.Static_libs = append(props.Static_libs, "all-updatable-modules-system-stubs-exportable")
+		props.Sdk_version = proptools.StringPtr("module_current")
+		props.Visibility = []string{"//frameworks/base"}
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+}
+
 func createMergedTestStubsForNonUpdatableModules(ctx android.LoadHookContext) {
 	props := libraryProps{}
 	props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs")
@@ -238,6 +271,15 @@
 	ctx.CreateModule(java.LibraryFactory, &props)
 }
 
+func createMergedTestExportableStubsForNonUpdatableModules(ctx android.LoadHookContext) {
+	props := libraryProps{}
+	props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs-exportable")
+	props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.exportable.test")
+	props.Sdk_version = proptools.StringPtr("module_current")
+	props.Visibility = []string{"//frameworks/base"}
+	ctx.CreateModule(java.LibraryFactory, &props)
+}
+
 func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) {
 	// This module is for the "framework-all" module, which should not include the core libraries.
 	modules = removeAll(modules, core_libraries_modules)
@@ -268,6 +310,19 @@
 	}
 }
 
+func createMergedFrameworkModuleLibExportableStubs(ctx android.LoadHookContext, modules []string) {
+	// The user of this module compiles against the "core" SDK and against non-updatable modules,
+	// so remove to avoid dupes.
+	modules = removeAll(modules, core_libraries_modules)
+	modules = removeAll(modules, non_updatable_modules)
+	props := libraryProps{}
+	props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api-exportable")
+	props.Static_libs = transformArray(modules, "", ".stubs.exportable.module_lib")
+	props.Sdk_version = proptools.StringPtr("module_current")
+	props.Visibility = []string{"//frameworks/base"}
+	ctx.CreateModule(java.LibraryFactory, &props)
+}
+
 func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []string) {
 	// The user of this module compiles against the "core" SDK and against non-updatable modules,
 	// so remove to avoid dupes.
@@ -383,6 +438,27 @@
 	}
 }
 
+func createFullExportableApiLibraries(ctx android.LoadHookContext) {
+	javaLibraryNames := []string{
+		"android_stubs_current_exportable",
+		"android_system_stubs_current_exportable",
+		"android_test_stubs_current_exportable",
+		"android_module_lib_stubs_current_exportable",
+		"android_system_server_stubs_current_exportable",
+	}
+
+	for _, libraryName := range javaLibraryNames {
+		props := libraryProps{}
+		props.Name = proptools.StringPtr(libraryName)
+		staticLib := libraryName + ".from-source"
+		props.Static_libs = []string{staticLib}
+		props.Defaults = []string{"android.jar_defaults"}
+		props.Visibility = []string{"//visibility:public"}
+
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+}
+
 func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
 	bootclasspath := a.properties.Bootclasspath
 	system_server_classpath := a.properties.System_server_classpath
@@ -398,6 +474,11 @@
 	createMergedFrameworkModuleLibStubs(ctx, bootclasspath)
 	createMergedFrameworkImpl(ctx, bootclasspath)
 
+	createMergedPublicExportableStubs(ctx, bootclasspath)
+	createMergedSystemExportableStubs(ctx, bootclasspath)
+	createMergedTestExportableStubsForNonUpdatableModules(ctx)
+	createMergedFrameworkModuleLibExportableStubs(ctx, bootclasspath)
+
 	createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath)
 
 	createPublicStubsSourceFilegroup(ctx, bootclasspath)
@@ -405,6 +486,8 @@
 	createApiContributionDefaults(ctx, bootclasspath)
 
 	createFullApiLibraries(ctx)
+
+	createFullExportableApiLibraries(ctx)
 }
 
 func combinedApisModuleFactory() android.Module {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 66d04a3..f9e7aeb 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3541,7 +3541,7 @@
          * foreground.  This may be running a window that is behind the current
          * foreground (so paused and with its state saved, not interacting with
          * the user, but visible to them to some degree); it may also be running
-         * other services under the system's control that it inconsiders important.
+         * other services under the system's control that it considers important.
          */
         public static final int IMPORTANCE_VISIBLE = 200;
 
@@ -3613,9 +3613,9 @@
         public static final int IMPORTANCE_CANT_SAVE_STATE = 350;
 
         /**
-         * Constant for {@link #importance}: This process process contains
-         * cached code that is expendable, not actively running any app components
-         * we care about.
+         * Constant for {@link #importance}: This process contains cached code
+         * that is expendable, not actively running any app components we care
+         * about.
          */
         public static final int IMPORTANCE_CACHED = 400;
 
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 561db9c..83b7eda 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -80,8 +80,6 @@
      * <p>The VCN will only migrate to a Carrier WiFi network that has a signal strength greater
      * than, or equal to this threshold.
      *
-     * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup.
-     *
      * @hide
      */
     @NonNull
@@ -94,8 +92,6 @@
      * <p>If the VCN's selected Carrier WiFi network has a signal strength less than this threshold,
      * the VCN will attempt to migrate away from the Carrier WiFi network.
      *
-     * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup.
-     *
      * @hide
      */
     @NonNull
@@ -120,6 +116,15 @@
     public static final String VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY =
             "vcn_network_selection_ipsec_packet_loss_percent_threshold";
 
+    /**
+     * Key for the list of timeouts in minute to stop penalizing an underlying network candidate
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY =
+            "vcn_network_selection_penalty_timeout_minutes_list";
+
     // TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz
 
     /**
@@ -168,6 +173,7 @@
                 VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
                 VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
                 VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
+                VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
                 VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
                 VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY,
                 VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY,
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2cc850d..7bad9c5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12961,6 +12961,16 @@
         @Readable
         public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
 
+
+        /**
+         * Whether to boot with 16K page size compatible kernel
+         * 1 = Boot with 16K kernel
+         * 0 = Boot with 4K kernel (default)
+         * @hide
+         */
+        @Readable
+        public static final String ENABLE_16K_PAGES = "enable_16k_pages";
+
         /** Timeout for package verification.
         * @hide */
         @Readable
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index bdaad2b..473b814 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -47,6 +47,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
+import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
@@ -337,7 +338,14 @@
                         "SplashScreenView");
                 ImageView imageView = new ImageView(viewContext);
                 imageView.setBackground(mIconDrawable);
-                viewHost.setView(imageView, mIconSize, mIconSize);
+                final int windowFlag = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+                final WindowManager.LayoutParams lp =
+                        new WindowManager.LayoutParams(mIconSize, mIconSize,
+                                WindowManager.LayoutParams.TYPE_APPLICATION, windowFlag,
+                                PixelFormat.TRANSPARENT);
+                viewHost.setView(imageView, lp);
                 SurfaceControlViewHost.SurfacePackage surfacePackage = viewHost.getSurfacePackage();
                 surfaceView.setChildSurfacePackage(surfacePackage);
                 view.mSurfacePackage = surfacePackage;
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index dcc9686..fbe1b8e 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -48,6 +48,7 @@
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
         <permission name="android.permission.REBOOT"/>
+        <permission name="android.permission.RECOVERY"/>
         <permission name="android.permission.STATUS_BAR"/>
         <permission name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"/>
         <permission name="android.permission.TETHER_PRIVILEGED"/>
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
index 1d433e7..943e3fc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
@@ -59,7 +59,7 @@
 
     @Override
     public boolean isAvailable() {
-        return true;
+        return mWifiManager != null;
     }
 
     @Override
@@ -70,10 +70,8 @@
     @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
-        if (isAvailable()) {
-            mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS);
-            updateConnectivity();
-        }
+        mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS);
+        updateConnectivity();
     }
 
     @Override
@@ -84,16 +82,16 @@
     @SuppressLint("HardwareIds")
     @Override
     protected void updateConnectivity() {
+        if (mWifiManager == null || mWifiMacAddress == null) {
+            return;
+        }
+
         final String[] macAddresses = mWifiManager.getFactoryMacAddresses();
         String macAddress = null;
         if (macAddresses != null && macAddresses.length > 0) {
             macAddress = macAddresses[0];
         }
 
-        if (mWifiMacAddress == null) {
-            return;
-        }
-
         if (TextUtils.isEmpty(macAddress) || macAddress.equals(WifiInfo.DEFAULT_MAC_ADDRESS)) {
             mWifiMacAddress.setSummary(R.string.status_unavailable);
         } else {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 91c72b5..bc93c5b 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -280,6 +280,7 @@
                     Settings.Global.ENABLE_ADB_INCREMENTAL_INSTALL_DEFAULT,
                     Settings.Global.ENABLE_MULTI_SLOT_TIMEOUT_MILLIS,
                     Settings.Global.ENHANCED_4G_MODE_ENABLED,
+                    Settings.Global.ENABLE_16K_PAGES, // Added for 16K developer option
                     Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
                     Settings.Global.ERROR_LOGCAT_PREFIX,
                     Settings.Global.EUICC_PROVISIONED,
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 9c82a8d..b8d2284 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2861,7 +2861,11 @@
         return true;
     }
 
-    private static void freezeBinderAndPackageCgroup(ArrayList<Pair<ProcessRecord, Boolean>> procs,
+    private static boolean unfreezePackageCgroup(int packageUID) {
+        return freezePackageCgroup(packageUID, false);
+    }
+
+    private static void freezeBinderAndPackageCgroup(List<Pair<ProcessRecord, Boolean>> procs,
                                                      int packageUID) {
         // Freeze all binder processes under the target UID (whose cgroup is about to be frozen).
         // Since we're going to kill these, we don't need to unfreze them later.
@@ -2869,12 +2873,9 @@
         // processes (forks) should not be Binder users.
         int N = procs.size();
         for (int i = 0; i < N; i++) {
-            final int uid = procs.get(i).first.uid;
             final int pid = procs.get(i).first.getPid();
             int nRetries = 0;
-            // We only freeze the cgroup of the target package, so we do not need to freeze the
-            // Binder interfaces of dependant processes in other UIDs.
-            if (pid > 0 && uid == packageUID) {
+            if (pid > 0) {
                 try {
                     int rc;
                     do {
@@ -2888,12 +2889,19 @@
         }
 
         // We freeze the entire UID (parent) cgroup so that newly-specialized processes also freeze
-        // despite being added to a new child cgroup. The cgroups of package dependant processes are
-        // not frozen, since it's possible this would freeze processes with no dependency on the
-        // package being killed here.
+        // despite being added to a child cgroup created after this call that would otherwise be
+        // unfrozen.
         freezePackageCgroup(packageUID, true);
     }
 
+    private static List<Pair<ProcessRecord, Boolean>> getUIDSublist(
+            List<Pair<ProcessRecord, Boolean>> procs, int startIdx) {
+        final int uid = procs.get(startIdx).first.uid;
+        int endIdx = startIdx + 1;
+        while (endIdx < procs.size() && procs.get(endIdx).first.uid == uid) ++endIdx;
+        return procs.subList(startIdx, endIdx);
+    }
+
     @GuardedBy({"mService", "mProcLock"})
     boolean killPackageProcessesLSP(String packageName, int appId,
             int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
@@ -2989,25 +2997,36 @@
             }
         }
 
-        final int packageUID = UserHandle.getUid(userId, appId);
-        final boolean doFreeze = appId >= Process.FIRST_APPLICATION_UID
-                              && appId <= Process.LAST_APPLICATION_UID;
-        if (doFreeze) {
-            freezeBinderAndPackageCgroup(procs, packageUID);
+        final boolean killingUserApp = appId >= Process.FIRST_APPLICATION_UID
+                                    && appId <= Process.LAST_APPLICATION_UID;
+
+        if (killingUserApp) {
+            procs.sort((o1, o2) -> Integer.compare(o1.first.uid, o2.first.uid));
         }
 
-        int N = procs.size();
-        for (int i=0; i<N; i++) {
-            final Pair<ProcessRecord, Boolean> proc = procs.get(i);
-            removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
-                    reasonCode, subReason, reason, !doFreeze /* async */);
+        int idx = 0;
+        while (idx < procs.size()) {
+            final List<Pair<ProcessRecord, Boolean>> uidProcs = getUIDSublist(procs, idx);
+            final int packageUID = uidProcs.get(0).first.uid;
+
+            // Do not freeze for system apps or for dependencies of the targeted package, but
+            // make sure to freeze the targeted package for all users if called with USER_ALL.
+            final boolean doFreeze = killingUserApp && UserHandle.getAppId(packageUID) == appId;
+
+            if (doFreeze) freezeBinderAndPackageCgroup(uidProcs, packageUID);
+
+            for (Pair<ProcessRecord, Boolean> proc : uidProcs) {
+                removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
+                        reasonCode, subReason, reason, !doFreeze /* async */);
+            }
+            killAppZygotesLocked(packageName, appId, userId, false /* force */);
+
+            if (doFreeze) unfreezePackageCgroup(packageUID);
+
+            idx += uidProcs.size();
         }
-        killAppZygotesLocked(packageName, appId, userId, false /* force */);
         mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
-        if (doFreeze) {
-            freezePackageCgroup(packageUID, false);
-        }
-        return N > 0;
+        return procs.size() > 0;
     }
 
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 4c42c2d..1d41401 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -141,7 +141,7 @@
                 // If internal storage of the system user fails to prepare on first boot, then
                 // things are *really* broken, so we might as well reboot to recovery right away.
                 try {
-                    Log.wtf(TAG, "prepareUserData failed for user " + userId, e);
+                    Log.e(TAG, "prepareUserData failed for user " + userId, e);
                     if (isNewUser && userId == UserHandle.USER_SYSTEM && volumeUuid == null) {
                         RecoverySystem.rebootPromptAndWipeUserData(mContext,
                                 "failed to prepare internal storage for system user");
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index fcc0de1..3094b18 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -1910,6 +1910,12 @@
                 // Transforms do not need to be persisted; the IkeSession will keep them alive
                 mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
 
+                if (direction == IpSecManager.DIRECTION_IN
+                        && mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                        && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+                    mUnderlyingNetworkController.updateInboundTransform(mUnderlying, transform);
+                }
+
                 // For inbound transforms, additionally allow forwarded traffic to bridge to DUN (as
                 // needed)
                 final Set<Integer> exposedCaps = mConnectionConfig.getAllExposedCapabilities();
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index 48df44b..3f8d39e 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -30,6 +30,7 @@
 import android.annotation.Nullable;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.IpSecTransform;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -52,6 +53,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
 import com.android.server.vcn.util.LogUtils;
 
 import java.util.ArrayList;
@@ -201,6 +203,14 @@
         NetworkCallback oldWifiExitRssiThresholdCallback = mWifiExitRssiThresholdCallback;
         List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks);
         mCellBringupCallbacks.clear();
+
+        if (mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+            for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+                evaluator.close();
+            }
+        }
+
         mUnderlyingNetworkRecords.clear();
 
         // Register new callbacks. Make-before-break; always register new callbacks before removal
@@ -417,11 +427,42 @@
         if (oldSnapshot
                 .getAllSubIdsInGroup(mSubscriptionGroup)
                 .equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) {
+
+            if (mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                    && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+                reevaluateNetworks();
+            }
             return;
         }
         registerOrUpdateNetworkRequests();
     }
 
+    /**
+     * Pass the IpSecTransform of the VCN to UnderlyingNetworkController for metric monitoring
+     *
+     * <p>Caller MUST call it when IpSecTransforms have been created for VCN creation or migration
+     */
+    public void updateInboundTransform(
+            @NonNull UnderlyingNetworkRecord currentNetwork, @NonNull IpSecTransform transform) {
+        if (!mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                || !mVcnContext.isFlagIpSecTransformStateEnabled()) {
+            logWtf("#updateInboundTransform: unexpected call; flags missing");
+            return;
+        }
+
+        Objects.requireNonNull(currentNetwork, "currentNetwork is null");
+        Objects.requireNonNull(transform, "transform is null");
+
+        if (mCurrentRecord == null
+                || mRouteSelectionCallback == null
+                || !Objects.equals(currentNetwork.network, mCurrentRecord.network)) {
+            // The caller (VcnGatewayConnection) is out-of-dated. Ignore this call.
+            return;
+        }
+
+        mUnderlyingNetworkRecords.get(mCurrentRecord.network).setInboundTransform(transform);
+    }
+
     /** Tears down this Tracker, and releases all underlying network requests. */
     public void teardown() {
         mVcnContext.ensureRunningOnLooperThread();
@@ -438,7 +479,7 @@
 
     private TreeSet<UnderlyingNetworkEvaluator> getSortedUnderlyingNetworks() {
         TreeSet<UnderlyingNetworkEvaluator> sorted =
-                new TreeSet<>(UnderlyingNetworkEvaluator.getComparator());
+                new TreeSet<>(UnderlyingNetworkEvaluator.getComparator(mVcnContext));
 
         for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
             if (evaluator.getPriorityClass() != NetworkPriorityClassifier.PRIORITY_INVALID) {
@@ -525,11 +566,17 @@
                             mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
                             mSubscriptionGroup,
                             mLastSnapshot,
-                            mCarrierConfig));
+                            mCarrierConfig,
+                            new NetworkEvaluatorCallbackImpl()));
         }
 
         @Override
         public void onLost(@NonNull Network network) {
+            if (mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                    && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+                mUnderlyingNetworkRecords.get(network).close();
+            }
+
             mUnderlyingNetworkRecords.remove(network);
 
             reevaluateNetworks();
@@ -598,6 +645,21 @@
         }
     }
 
+    @VisibleForTesting
+    class NetworkEvaluatorCallbackImpl implements NetworkEvaluatorCallback {
+        @Override
+        public void onEvaluationResultChanged() {
+            if (!mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                    || !mVcnContext.isFlagIpSecTransformStateEnabled()) {
+                logWtf("#onEvaluationResultChanged: unexpected call; flags missing");
+                return;
+            }
+
+            mVcnContext.ensureRunningOnLooperThread();
+            reevaluateNetworks();
+        }
+    }
+
     private String getLogPrefix() {
         return "("
                 + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
@@ -690,21 +752,22 @@
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public static class Dependencies {
-        /** Construct a new UnderlyingNetworkEvaluator */
         public UnderlyingNetworkEvaluator newUnderlyingNetworkEvaluator(
                 @NonNull VcnContext vcnContext,
                 @NonNull Network network,
                 @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
                 @NonNull ParcelUuid subscriptionGroup,
                 @NonNull TelephonySubscriptionSnapshot lastSnapshot,
-                @Nullable PersistableBundleWrapper carrierConfig) {
+                @Nullable PersistableBundleWrapper carrierConfig,
+                @NonNull NetworkEvaluatorCallback evaluatorCallback) {
             return new UnderlyingNetworkEvaluator(
                     vcnContext,
                     network,
                     underlyingNetworkTemplates,
                     subscriptionGroup,
                     lastSnapshot,
-                    carrierConfig);
+                    carrierConfig,
+                    evaluatorCallback);
         }
     }
 }
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
index c124a19..2f4cf5e 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -16,23 +16,32 @@
 
 package com.android.server.vcn.routeselection;
 
+import static com.android.server.VcnManagementService.LOCAL_LOG;
 import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.IpSecTransform;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.vcn.VcnManager;
 import android.net.vcn.VcnUnderlyingNetworkTemplate;
+import android.os.Handler;
 import android.os.ParcelUuid;
+import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.VcnContext;
 
+import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 
 /**
  * UnderlyingNetworkEvaluator evaluates the quality and priority class of a network candidate for
@@ -43,20 +52,41 @@
 public class UnderlyingNetworkEvaluator {
     private static final String TAG = UnderlyingNetworkEvaluator.class.getSimpleName();
 
+    private static final int[] PENALTY_TIMEOUT_MINUTES_DEFAULT = new int[] {5};
+
     @NonNull private final VcnContext mVcnContext;
+    @NonNull private final Handler mHandler;
+    @NonNull private final Object mCancellationToken = new Object();
+
     @NonNull private final UnderlyingNetworkRecord.Builder mNetworkRecordBuilder;
 
+    @NonNull private final NetworkEvaluatorCallback mEvaluatorCallback;
+    @NonNull private final List<NetworkMetricMonitor> mMetricMonitors = new ArrayList<>();
+
+    @NonNull private final Dependencies mDependencies;
+
+    // TODO: Support back-off timeouts
+    private long mPenalizedTimeoutMs;
+
     private boolean mIsSelected;
+    private boolean mIsPenalized;
     private int mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID;
 
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
     public UnderlyingNetworkEvaluator(
             @NonNull VcnContext vcnContext,
             @NonNull Network network,
             @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             @NonNull ParcelUuid subscriptionGroup,
             @NonNull TelephonySubscriptionSnapshot lastSnapshot,
-            @Nullable PersistableBundleWrapper carrierConfig) {
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkEvaluatorCallback evaluatorCallback,
+            @NonNull Dependencies dependencies) {
         mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
+        mHandler = new Handler(mVcnContext.getLooper());
+
+        mDependencies = Objects.requireNonNull(dependencies, "Missing dependencies");
+        mEvaluatorCallback = Objects.requireNonNull(evaluatorCallback, "Missing deps");
 
         Objects.requireNonNull(underlyingNetworkTemplates, "Missing underlyingNetworkTemplates");
         Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
@@ -66,9 +96,76 @@
                 new UnderlyingNetworkRecord.Builder(
                         Objects.requireNonNull(network, "Missing network"));
         mIsSelected = false;
+        mIsPenalized = false;
+        mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig);
 
         updatePriorityClass(
                 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+        if (isIpSecPacketLossDetectorEnabled()) {
+            try {
+                mMetricMonitors.add(
+                        mDependencies.newIpSecPacketLossDetector(
+                                mVcnContext,
+                                mNetworkRecordBuilder.getNetwork(),
+                                carrierConfig,
+                                new MetricMonitorCallbackImpl()));
+            } catch (IllegalAccessException e) {
+                // No action. Do not add anything to mMetricMonitors
+            }
+        }
+    }
+
+    public UnderlyingNetworkEvaluator(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkEvaluatorCallback evaluatorCallback) {
+        this(
+                vcnContext,
+                network,
+                underlyingNetworkTemplates,
+                subscriptionGroup,
+                lastSnapshot,
+                carrierConfig,
+                evaluatorCallback,
+                new Dependencies());
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        /** Get an IpSecPacketLossDetector instance */
+        public IpSecPacketLossDetector newIpSecPacketLossDetector(
+                @NonNull VcnContext vcnContext,
+                @NonNull Network network,
+                @Nullable PersistableBundleWrapper carrierConfig,
+                @NonNull NetworkMetricMonitor.NetworkMetricMonitorCallback callback)
+                throws IllegalAccessException {
+            return new IpSecPacketLossDetector(vcnContext, network, carrierConfig, callback);
+        }
+    }
+
+    /** Callback to notify caller to reevaluate network selection */
+    public interface NetworkEvaluatorCallback {
+        /**
+         * Called when mIsPenalized changed
+         *
+         * <p>When receiving this call, UnderlyingNetworkController should reevaluate all network
+         * candidates for VCN underlying network selection
+         */
+        void onEvaluationResultChanged();
+    }
+
+    private class MetricMonitorCallbackImpl
+            implements NetworkMetricMonitor.NetworkMetricMonitorCallback {
+        public void onValidationResultReceived() {
+            mVcnContext.ensureRunningOnLooperThread();
+
+            handleValidationResult();
+        }
     }
 
     private void updatePriorityClass(
@@ -91,8 +188,25 @@
         }
     }
 
-    public static Comparator<UnderlyingNetworkEvaluator> getComparator() {
+    private boolean isIpSecPacketLossDetectorEnabled() {
+        return isIpSecPacketLossDetectorEnabled(mVcnContext);
+    }
+
+    private static boolean isIpSecPacketLossDetectorEnabled(VcnContext vcnContext) {
+        return vcnContext.isFlagIpSecTransformStateEnabled()
+                && vcnContext.isFlagNetworkMetricMonitorEnabled();
+    }
+
+    /** Get the comparator for UnderlyingNetworkEvaluator */
+    public static Comparator<UnderlyingNetworkEvaluator> getComparator(VcnContext vcnContext) {
         return (left, right) -> {
+            if (isIpSecPacketLossDetectorEnabled(vcnContext)) {
+                if (left.mIsPenalized != right.mIsPenalized) {
+                    // A penalized network should have lower priority which means a larger index
+                    return left.mIsPenalized ? 1 : -1;
+                }
+            }
+
             final int leftIndex = left.mPriorityClass;
             final int rightIndex = right.mPriorityClass;
 
@@ -112,6 +226,64 @@
         };
     }
 
+    private static long getPenaltyTimeoutMs(@Nullable PersistableBundleWrapper carrierConfig) {
+        final int[] timeoutMinuteList;
+
+        if (carrierConfig != null) {
+            timeoutMinuteList =
+                    carrierConfig.getIntArray(
+                            VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
+                            PENALTY_TIMEOUT_MINUTES_DEFAULT);
+        } else {
+            timeoutMinuteList = PENALTY_TIMEOUT_MINUTES_DEFAULT;
+        }
+
+        // TODO: Add the support of back-off timeouts and return the full list
+        return TimeUnit.MINUTES.toMillis(timeoutMinuteList[0]);
+    }
+
+    private void handleValidationResult() {
+        final boolean wasPenalized = mIsPenalized;
+        mIsPenalized = false;
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            mIsPenalized |= monitor.isValidationFailed();
+        }
+
+        if (wasPenalized == mIsPenalized) {
+            return;
+        }
+
+        logInfo(
+                "#handleValidationResult: wasPenalized "
+                        + wasPenalized
+                        + " mIsPenalized "
+                        + mIsPenalized);
+
+        if (mIsPenalized) {
+            mHandler.postDelayed(
+                    new ExitPenaltyBoxRunnable(), mCancellationToken, mPenalizedTimeoutMs);
+        } else {
+            // Exit the penalty box
+            mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+        }
+        mEvaluatorCallback.onEvaluationResultChanged();
+    }
+
+    public class ExitPenaltyBoxRunnable implements Runnable {
+        @Override
+        public void run() {
+            if (!mIsPenalized) {
+                logWtf("Evaluator not being penalized but ExitPenaltyBoxRunnable was scheduled");
+                return;
+            }
+
+            // TODO: There might be a future metric monitor (e.g. ping) that will require the
+            // validation to pass before exiting the penalty box.
+            mIsPenalized = false;
+            mEvaluatorCallback.onEvaluationResultChanged();
+        }
+    }
+
     /** Set the NetworkCapabilities */
     public void setNetworkCapabilities(
             @NonNull NetworkCapabilities nc,
@@ -162,6 +334,10 @@
 
         updatePriorityClass(
                 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.setIsSelectedUnderlyingNetwork(isSelected);
+        }
     }
 
     /**
@@ -174,6 +350,35 @@
             @Nullable PersistableBundleWrapper carrierConfig) {
         updatePriorityClass(
                 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+        // The already scheduled event will not be affected. The followup events will be scheduled
+        // with the new timeout
+        mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig);
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.setCarrierConfig(carrierConfig);
+        }
+    }
+
+    /** Update the inbound IpSecTransform applied to the network */
+    public void setInboundTransform(@NonNull IpSecTransform transform) {
+        if (!mIsSelected) {
+            logWtf("setInboundTransform on an unselected evaluator");
+            return;
+        }
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.setInboundTransform(transform);
+        }
+    }
+
+    /** Close the evaluator and stop all the underlying network metric monitors */
+    public void close() {
+        mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.close();
+        }
     }
 
     /** Return whether this network evaluator is valid */
@@ -196,6 +401,11 @@
         return mPriorityClass;
     }
 
+    /** Return whether the network is being penalized */
+    public boolean isPenalized() {
+        return mIsPenalized;
+    }
+
     /** Dump the information of this instance */
     public void dump(IndentingPrintWriter pw) {
         pw.println("UnderlyingNetworkEvaluator:");
@@ -211,7 +421,22 @@
 
         pw.println("mIsSelected: " + mIsSelected);
         pw.println("mPriorityClass: " + mPriorityClass);
+        pw.println("mIsPenalized: " + mIsPenalized);
 
         pw.decreaseIndent();
     }
+
+    private String getLogPrefix() {
+        return "[Network " + mNetworkRecordBuilder.getNetwork() + "] ";
+    }
+
+    private void logInfo(String msg) {
+        Slog.i(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log("[INFO ] " + TAG + getLogPrefix() + msg);
+    }
+
+    private void logWtf(String msg) {
+        Slog.wtf(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log("[WTF ] " + TAG + getLogPrefix() + msg);
+    }
 }
diff --git a/services/profcollect/Android.bp b/services/profcollect/Android.bp
index 2040bb6..fe431f5 100644
--- a/services/profcollect/Android.bp
+++ b/services/profcollect/Android.bp
@@ -22,24 +22,25 @@
 }
 
 filegroup {
-  name: "services.profcollect-javasources",
-  srcs: ["src/**/*.java"],
-  path: "src",
-  visibility: ["//frameworks/base/services"],
+    name: "services.profcollect-javasources",
+    srcs: ["src/**/*.java"],
+    path: "src",
+    visibility: ["//frameworks/base/services"],
 }
 
 filegroup {
-  name: "services.profcollect-sources",
-  srcs: [
-    ":services.profcollect-javasources",
-    ":profcollectd_aidl",
-  ],
-  visibility: ["//frameworks/base/services:__subpackages__"],
+    name: "services.profcollect-sources",
+    srcs: [
+        ":services.profcollect-javasources",
+        ":profcollectd_aidl",
+    ],
+    visibility: ["//frameworks/base/services:__subpackages__"],
 }
 
 java_library_static {
-  name: "services.profcollect",
-  defaults: ["platform_service_defaults"],
-  srcs: [":services.profcollect-sources"],
-  libs: ["services.core"],
+    name: "services.profcollect",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.profcollect-sources"],
+    static_libs: ["services.core"],
+    libs: ["service-art.stubs.system_server"],
 }
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 4007672..582b712 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -41,12 +41,15 @@
 import com.android.internal.R;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.IoThread;
+import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.art.ArtManagerLocal;
 import com.android.server.wm.ActivityMetricsLaunchObserver;
 import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
+import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
 
@@ -261,6 +264,7 @@
         BackgroundThread.get().getThreadHandler().post(
                 () -> {
                     registerAppLaunchObserver();
+                    registerDex2oatObserver();
                     registerOTAObserver();
                 });
     }
@@ -304,6 +308,44 @@
         }
     }
 
+    private void registerDex2oatObserver() {
+        ArtManagerLocal aml = LocalManagerRegistry.getManager(ArtManagerLocal.class);
+        if (aml == null) {
+            Log.w(LOG_TAG, "Couldn't get ArtManagerLocal");
+            return;
+        }
+        aml.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
+                (snapshot, reason, defaultPackages, builder, passedSignal) -> {
+                    traceOnDex2oatStart();
+                });
+    }
+
+    private void traceOnDex2oatStart() {
+        if (mIProfcollect == null) {
+            return;
+        }
+        // Sample for a fraction of dex2oat runs.
+        final int traceFrequency =
+            DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
+                "dex2oat_trace_freq", 10);
+        int randomNum = ThreadLocalRandom.current().nextInt(100);
+        if (randomNum < traceFrequency) {
+            if (DEBUG) {
+                Log.d(LOG_TAG, "Tracing on dex2oat event");
+            }
+            BackgroundThread.get().getThreadHandler().post(() -> {
+                try {
+                    // Dex2oat could take a while before it starts. Add a short delay before start
+                    // tracing.
+                    Thread.sleep(1000);
+                    mIProfcollect.trace_once("dex2oat");
+                } catch (RemoteException | InterruptedException e) {
+                    Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
+                }
+            });
+        }
+    }
+
     private void registerOTAObserver() {
         UpdateEngine updateEngine = new UpdateEngine();
         updateEngine.bind(new UpdateEngineCallback() {
diff --git a/tests/Camera2Tests/CameraToo/tests/Android.bp b/tests/Camera2Tests/CameraToo/tests/Android.bp
new file mode 100644
index 0000000..8339a2c
--- /dev/null
+++ b/tests/Camera2Tests/CameraToo/tests/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "frameworks_base_license",
+    ],
+}
+
+android_test {
+    name: "CameraTooTests",
+    instrumentation_for: "CameraToo",
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    static_libs: [
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+    ],
+    data: [
+        ":CameraToo",
+    ],
+}
diff --git a/tests/Camera2Tests/CameraToo/tests/Android.mk b/tests/Camera2Tests/CameraToo/tests/Android.mk
deleted file mode 100644
index dfa64f1..0000000
--- a/tests/Camera2Tests/CameraToo/tests/Android.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := CameraTooTests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../NOTICE
-LOCAL_INSTRUMENTATION_FOR := CameraToo
-LOCAL_SDK_VERSION := current
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules mockito-target-minus-junit4
-
-include $(BUILD_PACKAGE)
diff --git a/tests/Camera2Tests/CameraToo/tests/AndroidTest.xml b/tests/Camera2Tests/CameraToo/tests/AndroidTest.xml
new file mode 100644
index 0000000..884c095
--- /dev/null
+++ b/tests/Camera2Tests/CameraToo/tests/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs CameraToo tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CameraTooTests.apk" />
+        <option name="test-file-name" value="CameraToo.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.example.android.camera2.cameratoo.tests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index f846164..20b7f1f 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -269,6 +269,7 @@
     @Test
     public void testCreatedTransformsAreApplied() throws Exception {
         verifyVcnTransformsApplied(mGatewayConnection, false /* expectForwardTransform */);
+        verify(mUnderlyingNetworkController).updateInboundTransform(any(), any());
     }
 
     @Test
@@ -327,6 +328,8 @@
                             eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
         }
 
+        verify(mUnderlyingNetworkController).updateInboundTransform(any(), any());
+
         assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
 
         final List<ChildSaProposal> saProposals =
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 4c7b25a..e29e462 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -223,6 +223,8 @@
         doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
         doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags();
         doReturn(true).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
+        doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
+        doReturn(true).when(mVcnContext).isFlagNetworkMetricMonitorEnabled();
 
         doReturn(mUnderlyingNetworkController)
                 .when(mDeps)
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index 355c221..6015e931 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -26,6 +26,8 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.net.IpSecConfig;
+import android.net.IpSecTransform;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -141,4 +143,8 @@
                         mock(Handler.class));
         setupSystemService(mContext, mPowerManager, Context.POWER_SERVICE, PowerManager.class);
     }
+
+    protected IpSecTransform makeDummyIpSecTransform() throws Exception {
+        return new IpSecTransform(mContext, new IpSecConfig());
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
index 992f102..588624b 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
@@ -47,6 +47,8 @@
 
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.IpSecConfig;
+import android.net.IpSecTransform;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -70,6 +72,7 @@
 import com.android.server.vcn.routeselection.UnderlyingNetworkController.NetworkBringupCallback;
 import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback;
 import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkListener;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -153,11 +156,13 @@
     @Mock private CarrierConfigManager mCarrierConfigManager;
     @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
     @Mock private UnderlyingNetworkControllerCallback mNetworkControllerCb;
+    @Mock private NetworkEvaluatorCallback mEvaluatorCallback;
     @Mock private Network mNetwork;
 
     @Spy private Dependencies mDependencies = new Dependencies();
 
     @Captor private ArgumentCaptor<UnderlyingNetworkListener> mUnderlyingNetworkListenerCaptor;
+    @Captor private ArgumentCaptor<NetworkEvaluatorCallback> mEvaluatorCallbackCaptor;
 
     private TestLooper mTestLooper;
     private VcnContext mVcnContext;
@@ -176,7 +181,7 @@
                                 mTestLooper.getLooper(),
                                 mVcnNetworkProvider,
                                 false /* isInTestMode */));
-        resetVcnContext();
+        resetVcnContext(mVcnContext);
 
         setupSystemService(
                 mContext,
@@ -202,10 +207,11 @@
                                         .getVcnUnderlyingNetworkPriorities(),
                                 SUB_GROUP,
                                 mSubscriptionSnapshot,
-                                null));
+                                null,
+                                mEvaluatorCallback));
         doReturn(mNetworkEvaluator)
                 .when(mDependencies)
-                .newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any());
+                .newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any(), any());
 
         mUnderlyingNetworkController =
                 new UnderlyingNetworkController(
@@ -217,9 +223,11 @@
                         mDependencies);
     }
 
-    private void resetVcnContext() {
-        reset(mVcnContext);
-        doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+    private void resetVcnContext(VcnContext vcnContext) {
+        reset(vcnContext);
+        doNothing().when(vcnContext).ensureRunningOnLooperThread();
+        doReturn(true).when(vcnContext).isFlagNetworkMetricMonitorEnabled();
+        doReturn(true).when(vcnContext).isFlagIpSecTransformStateEnabled();
     }
 
     // Package private for use in NetworkPriorityClassifierTest
@@ -245,11 +253,13 @@
         final ConnectivityManager cm = mock(ConnectivityManager.class);
         setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
         final VcnContext vcnContext =
-                new VcnContext(
-                        mContext,
-                        mTestLooper.getLooper(),
-                        mVcnNetworkProvider,
-                        true /* isInTestMode */);
+                spy(
+                        new VcnContext(
+                                mContext,
+                                mTestLooper.getLooper(),
+                                mVcnNetworkProvider,
+                                true /* isInTestMode */));
+        resetVcnContext(vcnContext);
 
         new UnderlyingNetworkController(
                 vcnContext,
@@ -554,6 +564,45 @@
         verify(mNetworkEvaluator).reevaluate(any(), any(), any(), any());
     }
 
+    @Test
+    public void testUpdateIpSecTransform() {
+        verifyRegistrationOnAvailableAndGetCallback();
+
+        final UnderlyingNetworkRecord expectedRecord =
+                getTestNetworkRecord(
+                        mNetwork,
+                        INITIAL_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        final IpSecTransform expectedTransform = new IpSecTransform(mContext, new IpSecConfig());
+
+        mUnderlyingNetworkController.updateInboundTransform(expectedRecord, expectedTransform);
+        verify(mNetworkEvaluator).setInboundTransform(expectedTransform);
+    }
+
+    @Test
+    public void testOnEvaluationResultChanged() {
+        verifyRegistrationOnAvailableAndGetCallback();
+
+        // Verify #reevaluateNetworks is called by checking #getNetworkRecord
+        verify(mNetworkEvaluator).getNetworkRecord();
+
+        // Trigger the callback
+        verify(mDependencies)
+                .newUnderlyingNetworkEvaluator(
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        mEvaluatorCallbackCaptor.capture());
+        mEvaluatorCallbackCaptor.getValue().onEvaluationResultChanged();
+
+        // Verify #reevaluateNetworks is called again
+        verify(mNetworkEvaluator, times(2)).getNetworkRecord();
+    }
+
     private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback() {
         return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES);
     }
@@ -682,7 +731,7 @@
 
         cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */);
 
-        verifyOnSelectedUnderlyingNetworkChanged(null);
+        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null);
     }
 
     @Test
@@ -690,6 +739,7 @@
         UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
 
         cb.onLost(mNetwork);
+        verify(mNetworkEvaluator).close();
 
         verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null);
     }
@@ -755,10 +805,11 @@
                                 underlyingNetworkTemplates,
                                 SUB_GROUP,
                                 mSubscriptionSnapshot,
-                                null));
+                                null,
+                                mEvaluatorCallback));
         doReturn(evaluator)
                 .when(mDependencies)
-                .newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any());
+                .newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any(), any());
 
         cb.onAvailable(network);
         cb.onCapabilitiesChanged(network, responseNetworkCaps);
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
index 985e70c..aa81efe 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
@@ -16,27 +16,65 @@
 
 package com.android.server.vcn.routeselection;
 
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY;
+
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID;
 import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 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.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.eq;
+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.IpSecTransform;
 import android.net.vcn.VcnGatewayConnectionConfig;
-import android.os.PersistableBundle;
+
+import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.Dependencies;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+import java.util.concurrent.TimeUnit;
 
 public class UnderlyingNetworkEvaluatorTest extends NetworkEvaluationTestBase {
-    private PersistableBundleWrapper mCarrierConfig;
+    private static final int PENALTY_TIMEOUT_MIN = 10;
+    private static final long PENALTY_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(PENALTY_TIMEOUT_MIN);
+
+    @Mock private PersistableBundleWrapper mCarrierConfig;
+    @Mock private IpSecPacketLossDetector mIpSecPacketLossDetector;
+    @Mock private Dependencies mDependencies;
+    @Mock private NetworkEvaluatorCallback mEvaluatorCallback;
+
+    @Captor private ArgumentCaptor<NetworkMetricMonitorCallback> mMetricMonitorCbCaptor;
+
+    private UnderlyingNetworkEvaluator mNetworkEvaluator;
 
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        mCarrierConfig = new PersistableBundleWrapper(new PersistableBundle());
+
+        when(mDependencies.newIpSecPacketLossDetector(any(), any(), any(), any()))
+                .thenReturn(mIpSecPacketLossDetector);
+
+        when(mCarrierConfig.getIntArray(
+                        eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject()))
+                .thenReturn(new int[] {PENALTY_TIMEOUT_MIN});
+
+        mNetworkEvaluator = newValidUnderlyingNetworkEvaluator();
     }
 
     private UnderlyingNetworkEvaluator newUnderlyingNetworkEvaluator() {
@@ -46,7 +84,34 @@
                 VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
                 SUB_GROUP,
                 mSubscriptionSnapshot,
+                mCarrierConfig,
+                mEvaluatorCallback,
+                mDependencies);
+    }
+
+    private UnderlyingNetworkEvaluator newValidUnderlyingNetworkEvaluator() {
+        final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator();
+
+        evaluator.setNetworkCapabilities(
+                CELL_NETWORK_CAPABILITIES,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
                 mCarrierConfig);
+        evaluator.setLinkProperties(
+                LINK_PROPERTIES,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        evaluator.setIsBlocked(
+                false /* isBlocked */,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+
+        return evaluator;
     }
 
     @Test
@@ -98,4 +163,174 @@
         assertEquals(2, evaluator.getPriorityClass());
         assertEquals(expectedRecord, evaluator.getNetworkRecord());
     }
+
+    private void checkSetSelectedNetwork(boolean isSelected) {
+        mNetworkEvaluator.setIsSelected(
+                isSelected,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        verify(mIpSecPacketLossDetector).setIsSelectedUnderlyingNetwork(isSelected);
+    }
+
+    @Test
+    public void testSetIsSelected_selected() throws Exception {
+        checkSetSelectedNetwork(true /* isSelectedExpected */);
+    }
+
+    @Test
+    public void testSetIsSelected_unselected() throws Exception {
+        checkSetSelectedNetwork(false /* isSelectedExpected */);
+    }
+
+    @Test
+    public void testSetIpSecTransform_onSelectedNetwork() throws Exception {
+        final IpSecTransform transform = makeDummyIpSecTransform();
+
+        // Make the network selected
+        mNetworkEvaluator.setIsSelected(
+                true,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        mNetworkEvaluator.setInboundTransform(transform);
+
+        verify(mIpSecPacketLossDetector).setInboundTransform(transform);
+    }
+
+    @Test
+    public void testSetIpSecTransform_onUnSelectedNetwork() throws Exception {
+        mNetworkEvaluator.setIsSelected(
+                false,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        mNetworkEvaluator.setInboundTransform(makeDummyIpSecTransform());
+
+        verify(mIpSecPacketLossDetector, never()).setInboundTransform(any());
+    }
+
+    @Test
+    public void close() throws Exception {
+        mNetworkEvaluator.close();
+
+        verify(mIpSecPacketLossDetector).close();
+        mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+        assertNull(mTestLooper.nextMessage());
+    }
+
+    private NetworkMetricMonitorCallback getMetricMonitorCbCaptor() throws Exception {
+        verify(mDependencies)
+                .newIpSecPacketLossDetector(any(), any(), any(), mMetricMonitorCbCaptor.capture());
+
+        return mMetricMonitorCbCaptor.getValue();
+    }
+
+    private void checkPenalizeNetwork() throws Exception {
+        assertFalse(mNetworkEvaluator.isPenalized());
+
+        // Validation failed
+        when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(true);
+        getMetricMonitorCbCaptor().onValidationResultReceived();
+
+        // Verify the evaluator is penalized
+        assertTrue(mNetworkEvaluator.isPenalized());
+        verify(mEvaluatorCallback).onEvaluationResultChanged();
+    }
+
+    @Test
+    public void testRcvValidationResult_penalizeNetwork_penaltyTimeout() throws Exception {
+        checkPenalizeNetwork();
+
+        // Penalty timeout
+        mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        // Verify the evaluator is not penalized
+        assertFalse(mNetworkEvaluator.isPenalized());
+        verify(mEvaluatorCallback, times(2)).onEvaluationResultChanged();
+    }
+
+    @Test
+    public void testRcvValidationResult_penalizeNetwork_passValidation() throws Exception {
+        checkPenalizeNetwork();
+
+        // Validation passed
+        when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(false);
+        getMetricMonitorCbCaptor().onValidationResultReceived();
+
+        // Verify the evaluator is not penalized and penalty timeout is canceled
+        assertFalse(mNetworkEvaluator.isPenalized());
+        verify(mEvaluatorCallback, times(2)).onEvaluationResultChanged();
+        mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+        assertNull(mTestLooper.nextMessage());
+    }
+
+    @Test
+    public void testRcvValidationResult_penalizeNetwork_closeEvaluator() throws Exception {
+        checkPenalizeNetwork();
+
+        mNetworkEvaluator.close();
+
+        // Verify penalty timeout is canceled
+        mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+        assertNull(mTestLooper.nextMessage());
+    }
+
+    @Test
+    public void testRcvValidationResult_PenaltyStateUnchanged() throws Exception {
+        assertFalse(mNetworkEvaluator.isPenalized());
+
+        // Validation passed
+        when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(false);
+        getMetricMonitorCbCaptor().onValidationResultReceived();
+
+        // Verifications
+        assertFalse(mNetworkEvaluator.isPenalized());
+        verify(mEvaluatorCallback, never()).onEvaluationResultChanged();
+    }
+
+    @Test
+    public void testSetCarrierConfig() throws Exception {
+        final int additionalTimeoutMin = 10;
+        when(mCarrierConfig.getIntArray(
+                        eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject()))
+                .thenReturn(new int[] {PENALTY_TIMEOUT_MIN + additionalTimeoutMin});
+
+        // Update evaluator and penalize the network
+        mNetworkEvaluator.reevaluate(
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        checkPenalizeNetwork();
+
+        // Verify penalty timeout is changed
+        mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+        assertNull(mTestLooper.nextMessage());
+        mTestLooper.moveTimeForward(TimeUnit.MINUTES.toMillis(additionalTimeoutMin));
+        assertNotNull(mTestLooper.nextMessage());
+
+        // Verify NetworkMetricMonitor is notified
+        verify(mIpSecPacketLossDetector).setCarrierConfig(any());
+    }
+
+    @Test
+    public void testCompare() throws Exception {
+        when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(true);
+        getMetricMonitorCbCaptor().onValidationResultReceived();
+
+        final UnderlyingNetworkEvaluator penalized = mNetworkEvaluator;
+        final UnderlyingNetworkEvaluator notPenalized = newValidUnderlyingNetworkEvaluator();
+
+        assertEquals(penalized.getPriorityClass(), notPenalized.getPriorityClass());
+
+        final int result =
+                UnderlyingNetworkEvaluator.getComparator(mVcnContext)
+                        .compare(penalized, notPenalized);
+        assertEquals(1, result);
+    }
 }
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk
deleted file mode 100644
index 6361f9b..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk
+++ /dev/null
@@ -1,2 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk
deleted file mode 100644
index 27b6068..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk
+++ /dev/null
@@ -1,32 +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.
-//
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_PACKAGE_NAME := AaptTestMergeOnly_App
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_EXPORT_PACKAGE_RESOURCES := true
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    AaptTestMergeOnly_LeafLib \
-    AaptTestMergeOnly_LocalLib
-include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk
deleted file mode 100644
index c084849..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk
+++ /dev/null
@@ -1,31 +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.
-//
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestMergeOnly_LeafLib
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-LOCAL_AAPT_FLAGS := --merge-only
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk
deleted file mode 100644
index 699ad79..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk
+++ /dev/null
@@ -1,31 +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.
-//
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestMergeOnly_LocalLib
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-LOCAL_AAPT_FLAGS := --merge-only
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/Android.mk
deleted file mode 100644
index 6361f9b..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/Android.mk
+++ /dev/null
@@ -1,2 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
deleted file mode 100644
index 98b7440..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_PACKAGE_NAME := AaptTestNamespace_App
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_EXPORT_PACKAGE_RESOURCES := true
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    AaptTestNamespace_LibOne \
-    AaptTestNamespace_LibTwo
-include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk
deleted file mode 100644
index dd41702..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestNamespace_LibOne
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-
-# We need this to retain the R.java generated for this library.
-LOCAL_JAR_EXCLUDE_FILES := none
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk
deleted file mode 100644
index 0d11bcb..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestNamespace_LibTwo
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-
-# We need this to retain the R.java generated for this library.
-LOCAL_JAR_EXCLUDE_FILES := none
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/Split/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/Split/Android.mk
deleted file mode 100644
index 30375728..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/Split/Android.mk
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_PACKAGE_NAME := AaptTestNamespace_Split
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_APK_LIBRARIES := AaptTestNamespace_App
-LOCAL_RES_LIBRARIES := AaptTestNamespace_App
-LOCAL_AAPT_FLAGS := --package-id 0x80 --rename-manifest-package com.android.aapt.namespace.app
-include $(BUILD_PACKAGE)