Merge "Avoid collectting certificates repeatedly in method ParsingPackageUtils.parseMonolithicPackage" into main
diff --git a/Android.bp b/Android.bp
index 80469d6..900fba0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -386,7 +386,6 @@
         // TODO(b/120066492): remove gps_debug and protolog.conf.json when the build
         // system propagates "required" properly.
         "gps_debug.conf",
-        "protolog.conf.json.gz",
         "framework-res",
         // any install dependencies should go into framework-minus-apex-install-dependencies
         // rather than here to avoid bloating incremental build time
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index ff1ea4f..712f3e5 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -42,6 +42,7 @@
 # ActivityThread
 per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS
 per-file ActivityThread.java = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ActivityThread.java = file:RESOURCES_OWNERS
 
 # Alarm
 per-file *Alarm* = file:/apex/jobscheduler/OWNERS
diff --git a/core/java/android/app/RESOURCES_OWNERS b/core/java/android/app/RESOURCES_OWNERS
index 5582803..fe37c0c 100644
--- a/core/java/android/app/RESOURCES_OWNERS
+++ b/core/java/android/app/RESOURCES_OWNERS
@@ -1,3 +1,5 @@
-rtmitchell@google.com
-toddke@google.com
+zyy@google.com
+jakmcbane@google.com
+branliu@google.com
+markpun@google.com
 patb@google.com
diff --git a/core/java/android/service/voice/OWNERS b/core/java/android/service/voice/OWNERS
index 763c79e..5f9f6bd 100644
--- a/core/java/android/service/voice/OWNERS
+++ b/core/java/android/service/voice/OWNERS
@@ -3,5 +3,4 @@
 include /core/java/android/app/assist/OWNERS
 
 # The owner here should not be assist owner
-liangyuchen@google.com
 adudani@google.com
diff --git a/core/java/com/android/internal/net/ConnectivityBlobStore.java b/core/java/com/android/internal/net/ConnectivityBlobStore.java
index 1b18485..f8eb5be 100644
--- a/core/java/com/android/internal/net/ConnectivityBlobStore.java
+++ b/core/java/com/android/internal/net/ConnectivityBlobStore.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.content.ContentValues;
 import android.database.Cursor;
+import android.database.DatabaseUtils;
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
 import android.os.Binder;
@@ -153,8 +154,11 @@
         final List<String> names = new ArrayList<String>();
         try (Cursor cursor = mDb.query(TABLENAME,
                 new String[] {"name"} /* columns */,
-                "owner=? AND name LIKE ?" /* selection */,
-                new String[] {Integer.toString(ownerUid), prefix + "%"} /* selectionArgs */,
+                "owner=? AND name LIKE ? ESCAPE '\\'" /* selection */,
+                new String[] {
+                        Integer.toString(ownerUid),
+                        DatabaseUtils.escapeForLike(prefix) + "%"
+                } /* selectionArgs */,
                 null /* groupBy */,
                 null /* having */,
                 "name ASC" /* orderBy */)) {
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 3aca751..d687ef3 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -1,6 +1,5 @@
 # Camera
-per-file *Camera*,*camera* = cychen@google.com, epeev@google.com, etalvala@google.com
-per-file *Camera*,*camera* = shuzhenwang@google.com, yinchiayeh@google.com, zhijunhe@google.com
+per-file *Camera*,*camera* = file:platform/frameworks/av:/camera/OWNERS
 
 # Connectivity
 per-file android_net_* = codewiz@google.com, jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com
diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
index de7244d..71c068d 100644
--- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
@@ -149,7 +149,7 @@
                 FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
         assertEquals("19.–22.01.2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY,
                 FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("19.01. – 22.04.2009",
+        assertEquals("19.01.\u2009–\u200922.04.2009",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
         assertEquals("19.01.2009\u2009\u2013\u200909.02.2012",
@@ -220,10 +220,10 @@
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, 0));
         assertEquals("19.–22. Jan. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY,
                 FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("Mo., 19. – Do., 22. Jan. 2009",
+        assertEquals("Mo., 19.\u2009–\u2009Do., 22. Jan. 2009",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
-        assertEquals("Montag, 19. – Donnerstag, 22. Januar 2009",
+        assertEquals("Montag, 19.\u2009–\u2009Donnerstag, 22. Januar 2009",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
 
         assertEquals("19. Januar\u2009\u2013\u200922. April 2009",
diff --git a/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java b/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java
index 68545cf..ad4ccc9 100644
--- a/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java
+++ b/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java
@@ -153,4 +153,41 @@
         final String[] actual = connectivityBlobStore.list(TEST_NAME /* prefix */);
         assertArrayEquals(expected, actual);
     }
+
+    @Test
+    public void testList_underscoreInPrefix() throws Exception {
+        final String prefix = TEST_NAME + "_";
+        final String[] unsortedNames = new String[] {
+                prefix + "000",
+                TEST_NAME + "123",
+        };
+        // The '_' in the prefix should not be treated as a wildcard so the only match is "000".
+        final String[] expected = new String[] {"000"};
+        final ConnectivityBlobStore connectivityBlobStore = createConnectivityBlobStore();
+
+        for (int i = 0; i < unsortedNames.length; i++) {
+            assertTrue(connectivityBlobStore.put(unsortedNames[i], TEST_BLOB));
+        }
+        final String[] actual = connectivityBlobStore.list(prefix);
+        assertArrayEquals(expected, actual);
+    }
+
+    @Test
+    public void testList_percentInPrefix() throws Exception {
+        final String prefix = "%" + TEST_NAME + "%";
+        final String[] unsortedNames = new String[] {
+                TEST_NAME + "12345",
+                prefix + "0",
+                "abc" + TEST_NAME + "987",
+        };
+        // The '%' in the prefix should not be treated as a wildcard so the only match is "0".
+        final String[] expected = new String[] {"0"};
+        final ConnectivityBlobStore connectivityBlobStore = createConnectivityBlobStore();
+
+        for (int i = 0; i < unsortedNames.length; i++) {
+            assertTrue(connectivityBlobStore.put(unsortedNames[i], TEST_BLOB));
+        }
+        final String[] actual = connectivityBlobStore.list(prefix);
+        assertArrayEquals(expected, actual);
+    }
 }
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 9320c14..d1aa8e9 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -1432,6 +1432,16 @@
               postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
         </font>
     </family>
+    <family lang="ja">
+        <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+            NotoSerifHentaigana.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+            NotoSerifHentaigana.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
     <family lang="ko">
         <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular">
             NotoSansCJK-Regular.ttc
diff --git a/media/OWNERS b/media/OWNERS
index 2e9276d..1e5a458 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -4,6 +4,7 @@
 essick@google.com
 etalvala@google.com
 hunga@google.com
+jchowdhary@google.com
 jmtrivi@google.com
 jsharkey@android.com
 lajos@google.com
diff --git a/media/java/android/media/AudioDescriptor.java b/media/java/android/media/AudioDescriptor.java
index 85a653c..b5cae2c 100644
--- a/media/java/android/media/AudioDescriptor.java
+++ b/media/java/android/media/AudioDescriptor.java
@@ -99,7 +99,7 @@
      * When encapsulation is required, only playback with {@link android.media.AudioTrack} API is
      * supported. But playback with {@link android.media.MediaPlayer} is not.
      * When an encapsulation type is required, the {@link AudioFormat} encoding selected when
-     * creating the {@link AudioTrack} must match the encapsulation type, e.g
+     * creating the {@link AudioTrack} must match the encapsulation type, e.g.
      * AudioFormat#ENCODING_IEC61937 for AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937.
      *
      * @return an integer representing the encapsulation type
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 3dfd572..cff5f20 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3460,7 +3460,7 @@
 
     /* modes for setMode/getMode/setRoute/getRoute */
     /**
-     * Audio harware modes.
+     * Audio hardware modes.
      */
     /**
      * Invalid audio mode.
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 45bf5c4..2018a39 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -65,6 +65,7 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Supplier;
 
 /**
  MediaCodec class can be used to access low-level media codecs, i.e. encoder/decoder components.
@@ -2014,6 +2015,23 @@
         }
     }
 
+    // HACKY(b/325389296): aconfig flag accessors may not work in all contexts where MediaCodec API
+    // is used, so allow accessors to fail. In those contexts use a default value, normally false.
+
+    /* package private */
+    static boolean GetFlag(Supplier<Boolean> flagValueSupplier) {
+        return GetFlag(flagValueSupplier, false /* defaultValue */);
+    }
+
+    /* package private */
+    static boolean GetFlag(Supplier<Boolean> flagValueSupplier, boolean defaultValue) {
+        try {
+            return flagValueSupplier.get();
+        } catch (java.lang.RuntimeException e) {
+            return defaultValue;
+        }
+    }
+
     private boolean mHasSurface = false;
 
     /**
@@ -2346,7 +2364,7 @@
         }
 
         // at the moment no codecs support detachable surface
-        if (android.media.codec.Flags.nullOutputSurface()) {
+        if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
             // Detached surface flag is only meaningful if surface is null. Otherwise, it is
             // ignored.
             if (surface == null && (flags & CONFIGURE_FLAG_DETACHED_SURFACE) != 0) {
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index abad460..8ff4305 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -23,6 +23,7 @@
 import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC;
 import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE;
 import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
+import static android.media.MediaCodec.GetFlag;
 
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
@@ -827,10 +828,10 @@
                 features.add(new Feature(FEATURE_MultipleFrames,   (1 << 5), false));
                 features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 6), false));
                 features.add(new Feature(FEATURE_LowLatency,       (1 << 7), true));
-                if (android.media.codec.Flags.dynamicColorAspects()) {
+                if (GetFlag(() -> android.media.codec.Flags.dynamicColorAspects())) {
                     features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true));
                 }
-                if (android.media.codec.Flags.nullOutputSurface()) {
+                if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
                     features.add(new Feature(FEATURE_DetachedSurface,     (1 << 9), true));
                 }
 
@@ -851,10 +852,10 @@
                 features.add(new Feature(FEATURE_QpBounds, (1 << 3), false));
                 features.add(new Feature(FEATURE_EncodingStatistics, (1 << 4), false));
                 features.add(new Feature(FEATURE_HdrEditing, (1 << 5), false));
-                if (android.media.codec.Flags.hlgEditing()) {
+                if (GetFlag(() -> android.media.codec.Flags.hlgEditing())) {
                     features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true));
                 }
-                if (android.media.codec.Flags.regionOfInterest()) {
+                if (GetFlag(() -> android.media.codec.Flags.regionOfInterest())) {
                     features.add(new Feature(FEATURE_Roi, (1 << 7), true));
                 }
 
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f8e207c..999507b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -329,7 +329,7 @@
         "WindowManager-Shell",
         "LowLightDreamLib",
         "motion_tool_lib",
-        "androidx.core_core-animation-testing-nodeps",
+        "androidx.core_core-animation-testing",
         "androidx.compose.ui_ui",
         "flag-junit",
         "platform-test-annotations",
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index b900ea0..e945f1e 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -41,7 +41,7 @@
     ],
 
     static_libs: [
-        "androidx.core_core-animation-nodeps",
+        "androidx.core_core-animation",
         "androidx.core_core-ktx",
         "androidx.annotation_annotation",
         "SystemUIShaderLib",
@@ -62,7 +62,7 @@
     ],
 
     static_libs: [
-        "androidx.core_core-animation-nodeps",
+        "androidx.core_core-animation",
         "androidx.core_core-ktx",
         "androidx.annotation_annotation",
     ],
diff --git a/services/Android.bp b/services/Android.bp
index 5cb8ec6..474d501 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -219,6 +219,7 @@
 
     required: [
         "libukey2_jni_shared",
+        "protolog.conf.json.gz",
     ],
 
     // Uncomment to enable output of certain warnings (deprecated, unchecked)
diff --git a/services/core/java/com/android/server/am/LmkdStatsReporter.java b/services/core/java/com/android/server/am/LmkdStatsReporter.java
index 507fd9e..8266299 100644
--- a/services/core/java/com/android/server/am/LmkdStatsReporter.java
+++ b/services/core/java/com/android/server/am/LmkdStatsReporter.java
@@ -45,6 +45,7 @@
     private static final int LOW_MEM_AND_SWAP_UTIL = 6;
     private static final int LOW_FILECACHE_AFTER_THRASHING = 7;
     private static final int LOW_MEM = 8;
+    private static final int DIRECT_RECL_STUCK = 9;
 
     /**
      * Processes the LMK_KILL_OCCURRED packet data
@@ -109,6 +110,8 @@
                 return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__LOW_FILECACHE_AFTER_THRASHING;
             case LOW_MEM:
                 return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__LOW_MEM;
+            case DIRECT_RECL_STUCK:
+                return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__DIRECT_RECL_STUCK;
             default:
                 return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__UNKNOWN;
         }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5a078dbf..0bfbee6 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4595,6 +4595,7 @@
             case AudioSystem.MODE_CALL_SCREENING:
             case AudioSystem.MODE_CALL_REDIRECT:
             case AudioSystem.MODE_COMMUNICATION_REDIRECT:
+            case AudioSystem.MODE_RINGTONE:
                 break;
             default:
                 // no-op is enough for all other values
diff --git a/services/core/java/com/android/server/inputmethod/LocaleUtils.java b/services/core/java/com/android/server/inputmethod/LocaleUtils.java
index 7d090db..b1348b3 100644
--- a/services/core/java/com/android/server/inputmethod/LocaleUtils.java
+++ b/services/core/java/com/android/server/inputmethod/LocaleUtils.java
@@ -46,29 +46,44 @@
      * @param desired The locale preferred by user.
      * @return A score based on the locale matching for the default subtype enabling.
      */
-    @IntRange(from = 1, to = 3)
+    @IntRange(from = 1, to = 4)
     private static byte calculateMatchingSubScore(@NonNull final ULocale supported,
             @NonNull final ULocale desired) {
         // Assuming supported/desired is fully expanded.
         if (supported.equals(desired)) {
-            return 3;  // Exact match.
+            return 4;  // Exact match.
         }
 
+        // addLikelySubtags is a maximization process as per
+        // https://www.unicode.org/reports/tr35/#Likely_Subtags
+        ULocale maxDesired = ULocale.addLikelySubtags(desired);
+
         // Skip language matching since it was already done in calculateMatchingScore.
 
         final String supportedScript = supported.getScript();
-        if (supportedScript.isEmpty() || !supportedScript.equals(desired.getScript())) {
+        if (supportedScript.isEmpty() || !supportedScript.equals(maxDesired.getScript())) {
             // TODO: Need subscript matching. For example, Hanb should match with Bopo.
             return 1;
         }
 
         final String supportedCountry = supported.getCountry();
-        if (supportedCountry.isEmpty() || !supportedCountry.equals(desired.getCountry())) {
+        if (supportedCountry.isEmpty() || !supportedCountry.equals(maxDesired.getCountry())) {
             return 2;
         }
 
         // Ignore others e.g. variants, extensions.
-        return 3;
+
+        // Since addLikelySubtags can canonicalize subtags, e.g. the deprecated country codes
+        // an locale with an identical script and country before addLikelySubtags is in favour,
+        // and a score of 4 is returned.
+        String desiredScript = desired.getScript();
+        String desiredCountry = desired.getCountry();
+        if ((desiredScript.isEmpty() || desiredScript.equals(maxDesired.getScript()))
+                && (desiredCountry.isEmpty() || desiredCountry.equals(maxDesired.getCountry()))) {
+            return 4;
+        } else {
+            return 3;
+        }
     }
 
     private static final class ScoreEntry implements Comparable<ScoreEntry> {
@@ -180,8 +195,7 @@
                             ULocale.forLocale(preferredLocale));
                 }
                 score[j] = calculateMatchingSubScore(
-                        preferredULocaleCache[j],
-                        ULocale.addLikelySubtags(ULocale.forLocale(locale)));
+                        preferredULocaleCache[j], ULocale.forLocale(locale));
                 if (canSkip && score[j] != 0) {
                     canSkip = false;
                 }
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 698f6ea..4eea8c6 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -3708,6 +3708,7 @@
         public abstract T instantiateObject();
     }
 
+    @SuppressWarnings("ParcelableCreator")
     public static class ControllerActivityCounterImpl extends ControllerActivityCounter
             implements Parcelable {
         private final Clock mClock;
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index f3d7dd1..ed9fa65 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -210,6 +210,18 @@
     }
 
     @Override
+    public void onLinkPropertiesOrCapabilitiesChanged() {
+        if (!isStarted()) return;
+
+        reschedulePolling();
+    }
+
+    private void reschedulePolling() {
+        mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+        mHandler.postDelayed(new PollIpSecStateRunnable(), mCancellationToken, 0L);
+    }
+
+    @Override
     protected void start() {
         super.start();
         clearTransformStateAndPollingEvents();
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
index 4bacf3b..a1b212f 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
@@ -186,6 +186,11 @@
         // Subclasses MUST override it if they care
     }
 
+    /** Called when LinkProperties or NetworkCapabilities have changed */
+    public void onLinkPropertiesOrCapabilitiesChanged() {
+        // Subclasses MUST override it if they care
+    }
+
     public boolean isValidationFailed() {
         return mIsValidationFailed;
     }
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 2f4cf5e..78e06d4 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -25,6 +25,7 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.vcn.Flags;
 import android.net.vcn.VcnManager;
 import android.net.vcn.VcnUnderlyingNetworkTemplate;
 import android.os.Handler;
@@ -295,6 +296,12 @@
 
         updatePriorityClass(
                 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+        if (Flags.evaluateIpsecLossOnLpNcChange()) {
+            for (NetworkMetricMonitor monitor : mMetricMonitors) {
+                monitor.onLinkPropertiesOrCapabilitiesChanged();
+            }
+        }
     }
 
     /** Set the LinkProperties */
@@ -308,6 +315,12 @@
 
         updatePriorityClass(
                 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+        if (Flags.evaluateIpsecLossOnLpNcChange()) {
+            for (NetworkMetricMonitor monitor : mMetricMonitors) {
+                monitor.onLinkPropertiesOrCapabilitiesChanged();
+            }
+        }
     }
 
     /** Set whether the network is blocked */
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index e06f215..79eb0dc 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -24,3 +24,5 @@
 per-file Background*Start* = file:/BAL_OWNERS
 per-file Background*Start* = ogunwale@google.com, louischang@google.com
 
+# File related to activity callers
+per-file ActivityCallerState.java = file:/core/java/android/app/COMPONENT_CALLER_OWNERS
diff --git a/services/proguard.flags b/services/proguard.flags
index 88561b4..a01e7dc 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -54,7 +54,10 @@
 -keep,allowoptimization,allowaccessmodification class android.app.admin.flags.FeatureFlagsImpl { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.input.NativeInputManagerService$NativeImpl { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.ThreadPriorityBooster { *; }
--keep,allowaccessmodification class android.app.admin.flags.Flags { *; }
+
+# Keep all aconfig Flag class as they might be statically referenced by other packages
+# An merge or inlining could lead to missing dependencies that cause run time errors
+-keepclassmembernames class android.**.Flags, com.android.**.Flags { public *; }
 
 # Referenced via CarServiceHelperService in car-frameworks-service (avoid removing)
 -keep public class com.android.server.utils.Slogf { *; }
diff --git a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
index 9115f95..08155c7 100644
--- a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
+++ b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
@@ -3158,13 +3158,6 @@
         assertEquals(profile, ikev2VpnProfile.toVpnProfile());
     }
 
-    private void assertTransportInfoMatches(NetworkCapabilities nc, int type) {
-        assertNotNull(nc);
-        VpnTransportInfo ti = (VpnTransportInfo) nc.getTransportInfo();
-        assertNotNull(ti);
-        assertEquals(type, ti.getType());
-    }
-
     // Make it public and un-final so as to spy it
     public class TestDeps extends Vpn.Dependencies {
         TestDeps() {}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index fad8115..c333eb7 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -255,3 +255,89 @@
         "done && " +
         "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res",
 }
+
+FLAKY_AND_IGNORED = [
+    "androidx.test.filters.FlakyTest",
+    "org.junit.Ignore",
+]
+// Used by content protection TEST_MAPPING
+test_module_config {
+    name: "FrameworksServicesTests_contentprotection",
+    base: "FrameworksServicesTests",
+    test_suites: ["general-tests"],
+    include_filters: ["com.android.server.contentprotection"],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_om",
+    base: "FrameworksServicesTests",
+    test_suites: ["general-tests"],
+    include_filters: ["com.android.server.om."],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+// Used by contexthub TEST_MAPPING
+test_module_config {
+    name: "FrameworksServicesTests_contexthub_presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["general-tests"],
+    include_filters: ["com.android.server.location.contexthub."],
+    // TODO(ron): are these right, does it run anything?
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_contexthub_postsubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["general-tests"],
+    include_filters: ["com.android.server.location.contexthub."],
+    // TODO(ron): are these right, does it run anything?
+    include_annotations: ["android.platform.test.annotations.Postsubmit"],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+// Used by contentcapture
+test_module_config {
+    name: "FrameworksServicesTests_contentcapture",
+    base: "FrameworksServicesTests",
+    test_suites: ["general-tests"],
+    include_filters: ["com.android.server.contentcapture"],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_recoverysystem",
+    base: "FrameworksServicesTests",
+    test_suites: ["general-tests"],
+    include_filters: ["com.android.server.recoverysystem."],
+    exclude_annotations: ["androidx.test.filters.FlakyTest"],
+}
+
+// server pm TEST_MAPPING
+test_module_config {
+    name: "FrameworksServicesTests_pm_presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["general-tests"],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    include_filters: ["com.android.server.pm."],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_pm_postsubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["general-tests"],
+    include_annotations: ["android.platform.test.annotations.Postsubmit"],
+    include_filters: ["com.android.server.pm."],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+// server os TEST_MAPPING
+test_module_config {
+    name: "FrameworksServicesTests_os",
+    base: "FrameworksServicesTests",
+    test_suites: ["general-tests"],
+    include_filters: ["com.android.server.os."],
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
index 0ffa891..dae8f93 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
@@ -14,5 +14,11 @@
         }
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      // b/331020193, Move to presubmit early april 2024
+      "name": "FrameworksServicesTests_contentcapture"
+    }
   ]
 }
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
index 419508c..32729a8 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
@@ -14,5 +14,11 @@
         }
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      // b/331020193, Move to presubmit early april 2024
+      "name": "FrameworksServicesTests_contentprotection"
+    }
   ]
 }
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java
index 255cb64..26a2bee 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java
@@ -357,6 +357,29 @@
             assertEquals(1, dest.size());
             assertEquals(availableLocales.get(0), dest.get(0));  // "sr-Latn-RS"
         }
+        // Locale with deprecated subtag, e.g. CS for Serbia and Montenegro, should not win
+        // even if the other available locale doesn't have explicit script / country.
+        // On Android, users don't normally use deprecated subtags unless the application requests.
+        {
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-RS");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS"));
+            availableLocales.add(Locale.forLanguageTag("sr-RS"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(1), dest.get(0));  // "sr-RS"
+        }
+        {
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-RS");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS"));
+            availableLocales.add(Locale.forLanguageTag("sr"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(1), dest.get(0));  // "sr"
+        }
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
index 6035250..dc8f934 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
@@ -20,12 +20,18 @@
   ],
   "postsubmit": [
     {
+      // b/331020193, Move to presubmit early april 2024
+      "name": "FrameworksServicesTests_contexthub_presubmit"
+    },
+    {
       "name": "FrameworksServicesTests",
       "options": [
         {
           "include-filter": "com.android.server.location.contexthub."
         },
         {
+          // I believe this include annotation is preventing tests from being run
+          // as there are no matching tests with the Postsubmit annotation.
           "include-annotation": "android.platform.test.annotations.Postsubmit"
         },
         {
diff --git a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
index 558e259..41c4383 100644
--- a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
@@ -16,5 +16,11 @@
         }
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      // b/331020193, Move to presubmit early april 2024
+      "name": "FrameworksServicesTests_om"
+    }
   ]
 }
diff --git a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
index 5a46f8c4..06e7002 100644
--- a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
@@ -8,5 +8,11 @@
         }
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      // b/331020193, Move to presubmit early april 2024
+      "name": "FrameworksServicesTests_os"
+    }
   ]
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
index 85a73bb..f4e724f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
@@ -20,21 +20,13 @@
   ],
   "postsubmit": [
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.pm."
-        },
-        {
-          "include-annotation": "android.platform.test.annotations.Postsubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      // Presubmit is intentional here while testing with SLO checker.
+      // b/331020193, Move to presubmit early april 2024
+      "name": "FrameworksServicesTests_pm_presubmit"
+    },
+    {
+      // Leave postsubmit here when migrating
+      "name": "FrameworksServicesTests_pm_postsubmit"
     }
   ]
 }
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
index e9d8b2e..7e7393c 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
@@ -11,5 +11,11 @@
                 }
             ]
         }
+    ],
+    "postsubmit": [
+      {
+        // b/331020193, Move to presubmit early april 2024
+        "name": "FrameworksServicesTests_recoverysystem"
+      }
     ]
 }
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index 887e5ee..e443696 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -304,8 +304,7 @@
         mContentRecorder.updateRecording();
 
         // Resize the output surface.
-        final Point newSurfaceSize = new Point(Math.round(mSurfaceSize.x / 2f),
-                Math.round(mSurfaceSize.y * 2));
+        final Point newSurfaceSize = new Point(Math.round(mSurfaceSize.x / 2f), mSurfaceSize.y * 2);
         doReturn(newSurfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
                 anyInt());
         mContentRecorder.onConfigurationChanged(
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 55a00fc..48fc2dc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -799,6 +799,7 @@
         verify(child).onConfigurationChanged(any());
     }
 
+    @SuppressWarnings("SelfComparison")
     @Test
     public void testCompareTo() {
         final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
index d9cbea9..ed89190 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
@@ -181,7 +181,7 @@
                 // We only need to look at the broadcast events that occurred before
                 // this notification related event.
                 while (dispatchTimestampsMs.size() > 0
-                        && dispatchTimestampsMs.peekFirst() < timestampMs) {
+                        && dispatchTimestampsMs.peekFirst() <= timestampMs) {
                     final long dispatchTimestampMs = dispatchTimestampsMs.peekFirst();
                     final long elapsedDurationMs = timestampMs - dispatchTimestampMs;
                     // Only increment the counts if the broadcast was sent not too long ago, as
diff --git a/test-legacy/Android.bp b/test-legacy/Android.bp
new file mode 100644
index 0000000..236d704
--- /dev/null
+++ b/test-legacy/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+    ],
+}
+
+java_library {
+    name: "android.test.legacy",
+    sdk_version: "current",
+    libs: [
+        "android.test.mock.stubs",
+        "junit",
+    ],
+    static_libs: [
+        "android.test.base-minus-junit",
+        "android.test.runner-minus-junit",
+    ],
+    dist: {
+        targets: [
+            "sdk",
+        ],
+    },
+}
diff --git a/test-legacy/Android.mk b/test-legacy/Android.mk
deleted file mode 100644
index da9dc25..0000000
--- a/test-legacy/Android.mk
+++ /dev/null
@@ -1,50 +0,0 @@
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-
-# For unbundled build we'll use the prebuilt jar from prebuilts/sdk.
-ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)))
-
-# Build the android.test.legacy library
-# =====================================
-# Built against the SDK so that it can be statically included in APKs
-# without breaking link type checks.
-#
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := android.test.legacy
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-MIT SPDX-license-identifier-Unicode-DFS
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
-
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_LIBRARIES := junit android.test.mock.stubs
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    android.test.base-minus-junit \
-    android.test.runner-minus-junit \
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-$(call declare-license-metadata,$(full_classes_jar),\
-    SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-MIT SPDX-license-identifier-Unicode-DFS,\
-    notice,$(LOCAL_PATH)/../NOTICE,Android,frameworks/base)
-
-# Archive a copy of the classes.jar in SDK build.
-$(call dist-for-goals,sdk,$(full_classes_jar):android.test.legacy.jar)
-
-endif  # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index ba9e4a8..f82d9ca 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -130,14 +130,13 @@
     private static final Pattern PATTERN_SYSTEM_FONT_FILES =
             Pattern.compile("^/(system|product)/fonts/");
 
-    private String mKeyId;
     private FontManager mFontManager;
     private UiDevice mUiDevice;
 
     @Before
     public void setUp() throws Exception {
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        mKeyId = insertCert(CERT_PATH);
+        insertCert(CERT_PATH);
         mFontManager = context.getSystemService(FontManager.class);
         expectCommandToSucceed("cmd font clear");
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
@@ -147,9 +146,6 @@
     public void tearDown() throws Exception {
         // Ignore errors because this may fail if updatable system font is not enabled.
         runShellCommand("cmd font clear", null);
-        if (mKeyId != null) {
-            expectCommandToSucceed("mini-keyctl unlink " + mKeyId + " .fs-verity");
-        }
     }
 
     @Test
@@ -369,20 +365,11 @@
         assertThat(isFileOpenedBy(fontPath, EMOJI_RENDERING_TEST_APP_ID)).isFalse();
     }
 
-    private static String insertCert(String certPath) throws Exception {
-        Pair<String, String> result;
-        try (InputStream is = new FileInputStream(certPath)) {
-            result = runShellCommand("mini-keyctl padd asymmetric fsv_test .fs-verity", is);
-        }
+    private static void insertCert(String certPath) throws Exception {
         // /data/local/tmp is not readable by system server. Copy a cert file to /data/fonts
         final String copiedCert = "/data/fonts/debug_cert.der";
         runShellCommand("cp " + certPath + " " + copiedCert, null);
         runShellCommand("cmd font install-debug-cert " + copiedCert, null);
-        // Assert that there are no errors.
-        assertThat(result.second).isEmpty();
-        String keyId = result.first.trim();
-        assertThat(keyId).matches("^\\d+$");
-        return keyId;
     }
 
     private int updateFontFile(String fontPath, String signaturePath) throws IOException {
diff --git a/tests/testables/OWNERS b/tests/testables/OWNERS
new file mode 100644
index 0000000..a6f1632
--- /dev/null
+++ b/tests/testables/OWNERS
@@ -0,0 +1 @@
+file:/packages/SystemUI/OWNERS
\ No newline at end of file
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
index 5107943..fdf8fb8 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -34,6 +34,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -417,4 +418,31 @@
         checkGetPacketLossRate(oldState, 20000, 14000, 4096, 19);
         checkGetPacketLossRate(oldState, 20000, 14000, 3000, 10);
     }
+
+    // Verify the polling event is scheduled with expected delays
+    private void verifyPollEventDelayAndScheduleNext(long expectedDelayMs) {
+        if (expectedDelayMs > 0) {
+            mTestLooper.dispatchAll();
+            verify(mIpSecTransform, never()).requestIpSecTransformState(any(), any());
+            mTestLooper.moveTimeForward(expectedDelayMs);
+        }
+
+        mTestLooper.dispatchAll();
+        verify(mIpSecTransform).requestIpSecTransformState(any(), any());
+        reset(mIpSecTransform);
+    }
+
+    @Test
+    public void testOnLinkPropertiesOrCapabilitiesChange() throws Exception {
+        // Start the monitor; verify the 1st poll is scheduled without delay
+        startMonitorAndCaptureStateReceiver();
+        verifyPollEventDelayAndScheduleNext(0 /* expectedDelayMs */);
+
+        // Verify the 2nd poll is rescheduled without delay
+        mIpSecPacketLossDetector.onLinkPropertiesOrCapabilitiesChanged();
+        verifyPollEventDelayAndScheduleNext(0 /* expectedDelayMs */);
+
+        // Verify the 3rd poll is scheduled with configured delay
+        verifyPollEventDelayAndScheduleNext(POLL_IPSEC_STATE_INTERVAL_MS);
+    }
 }
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 444208e..af6daa1 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -122,6 +122,7 @@
         MockitoAnnotations.initMocks(this);
 
         mSetFlagsRule.enableFlags(Flags.FLAG_VALIDATE_NETWORK_ON_IPSEC_LOSS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_EVALUATE_IPSEC_LOSS_ON_LP_NC_CHANGE);
 
         when(mNetwork.getNetId()).thenReturn(-1);
 
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 aa81efe..1d68721 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -333,4 +334,36 @@
                         .compare(penalized, notPenalized);
         assertEquals(1, result);
     }
+
+    @Test
+    public void testNotifyNetworkMetricMonitorOnLpChange() throws Exception {
+        // Clear calls invoked when initializing mNetworkEvaluator
+        reset(mIpSecPacketLossDetector);
+
+        final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator();
+        evaluator.setNetworkCapabilities(
+                CELL_NETWORK_CAPABILITIES,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+
+        verify(mIpSecPacketLossDetector).onLinkPropertiesOrCapabilitiesChanged();
+    }
+
+    @Test
+    public void testNotifyNetworkMetricMonitorOnNcChange() throws Exception {
+        // Clear calls invoked when initializing mNetworkEvaluator
+        reset(mIpSecPacketLossDetector);
+
+        final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator();
+        evaluator.setLinkProperties(
+                LINK_PROPERTIES,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+
+        verify(mIpSecPacketLossDetector).onLinkPropertiesOrCapabilitiesChanged();
+    }
 }