Merge "[cts/SettingsProviderPerfTest] use an allowlisted namespace" into main
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 2587969..5f32ba0 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -43,29 +43,14 @@
     ],
     out: [
         "ravenwood.jar",
-
-        // Following files are created just as FYI.
-        "hoststubgen_framework-minus-apex_keep_all.txt",
-        "hoststubgen_framework-minus-apex_dump.txt",
-
         "hoststubgen_framework-minus-apex.log",
-        "hoststubgen_framework-minus-apex_stats.csv",
-        "hoststubgen_framework-minus-apex_apis.csv",
     ],
 }
 
 framework_minus_apex_cmd = "$(location hoststubgen) " +
     "@$(location :ravenwood-standard-options) " +
-
     "--debug-log $(location hoststubgen_framework-minus-apex.log) " +
-    "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " +
-    "--supported-api-list-file $(location hoststubgen_framework-minus-apex_apis.csv) " +
-
     "--out-impl-jar $(location ravenwood.jar) " +
-
-    "--gen-keep-all-file $(location hoststubgen_framework-minus-apex_keep_all.txt) " +
-    "--gen-input-dump-file $(location hoststubgen_framework-minus-apex_dump.txt) " +
-
     "--in-jar $(location :framework-minus-apex-for-hoststubgen) " +
     "--policy-override-file $(location :ravenwood-framework-policies) " +
     "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) "
@@ -133,13 +118,26 @@
 // Build framework-minus-apex.ravenwood-base without sharding.
 // We extract the various dump files from this one, rather than the sharded ones, because
 // some dumps use the output from other classes (e.g. base classes) which may not be in the
-// same shard.
+// same shard. Also some of the dump files ("apis") may be slow even when sharded, because
+// the output contains the information from all the input classes, rather than the output classes.
 // Not using sharding is fine for this module because it's only used for collecting the
 // dump / stats files, which don't have to happen regularly.
 java_genrule {
     name: "framework-minus-apex.ravenwood-base_all",
     defaults: ["framework-minus-apex.ravenwood-base_defaults"],
-    cmd: framework_minus_apex_cmd,
+    cmd: framework_minus_apex_cmd +
+        "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " +
+        "--supported-api-list-file $(location hoststubgen_framework-minus-apex_apis.csv) " +
+
+        "--gen-keep-all-file $(location hoststubgen_framework-minus-apex_keep_all.txt) " +
+        "--gen-input-dump-file $(location hoststubgen_framework-minus-apex_dump.txt) ",
+
+    out: [
+        "hoststubgen_framework-minus-apex_keep_all.txt",
+        "hoststubgen_framework-minus-apex_dump.txt",
+        "hoststubgen_framework-minus-apex_stats.csv",
+        "hoststubgen_framework-minus-apex_apis.csv",
+    ],
 }
 
 // Marge all the sharded jars
diff --git a/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java b/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java
index c34936f..fbe67a4 100644
--- a/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java
@@ -19,7 +19,6 @@
 import android.graphics.Paint;
 import android.graphics.RecordingCanvas;
 import android.graphics.RenderNode;
-import android.graphics.Typeface;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 
@@ -121,34 +120,13 @@
     public void testSetFontVariationSettings() {
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         final Paint paint = new Paint(PAINT);
+        final Random random = new Random(0);
         while (state.keepRunning()) {
             state.pauseTiming();
-            paint.setTypeface(null);
-            paint.setFontVariationSettings(null);
-            Typeface.clearTypefaceCachesForTestingPurpose();
+            int weight = random.nextInt(1000);
             state.resumeTiming();
 
-            paint.setFontVariationSettings("'wght' 450");
+            paint.setFontVariationSettings("'wght' " + weight);
         }
-        Typeface.clearTypefaceCachesForTestingPurpose();
     }
-
-    @Test
-    public void testSetFontVariationSettings_Cached() {
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        final Paint paint = new Paint(PAINT);
-        Typeface.clearTypefaceCachesForTestingPurpose();
-
-        while (state.keepRunning()) {
-            state.pauseTiming();
-            paint.setTypeface(null);
-            paint.setFontVariationSettings(null);
-            state.resumeTiming();
-
-            paint.setFontVariationSettings("'wght' 450");
-        }
-
-        Typeface.clearTypefaceCachesForTestingPurpose();
-    }
-
 }
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index fd4d8e9..0cc210b 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -1836,9 +1836,10 @@
                     // have shared library asset paths appended if there are any.
                     if (r.getImpl() != null) {
                         final ResourcesImpl oldImpl = r.getImpl();
+                        final AssetManager oldAssets = oldImpl.getAssets();
                         // ResourcesImpl constructor will help to append shared library asset paths.
-                        if (oldImpl.getAssets().isUpToDate()) {
-                            final ResourcesImpl newImpl = new ResourcesImpl(oldImpl.getAssets(),
+                        if (oldAssets != AssetManager.getSystem() && oldAssets.isUpToDate()) {
+                            final ResourcesImpl newImpl = new ResourcesImpl(oldAssets,
                                     oldImpl.getMetrics(), oldImpl.getConfiguration(),
                                     oldImpl.getDisplayAdjustments());
                             r.setImpl(newImpl);
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
index 0c55ed5..9bd4860 100644
--- a/core/java/android/hardware/camera2/params/SessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -17,8 +17,6 @@
 
 package android.hardware.camera2.params;
 
-import static com.android.internal.util.Preconditions.*;
-
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
@@ -32,8 +30,6 @@
 import android.hardware.camera2.CameraDevice.CameraDeviceSetup;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.params.InputConfiguration;
-import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.utils.HashCodeHelpers;
 import android.media.ImageReader;
 import android.os.Parcel;
@@ -46,6 +42,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -95,8 +92,8 @@
     public @interface SessionMode {};
 
     // Camera capture session related parameters.
-    private List<OutputConfiguration> mOutputConfigurations;
-    private CameraCaptureSession.StateCallback mStateCallback;
+    private final @NonNull List<OutputConfiguration> mOutputConfigurations;
+    private CameraCaptureSession.StateCallback mStateCallback = null;
     private int mSessionType;
     private Executor mExecutor = null;
     private InputConfiguration mInputConfig = null;
@@ -268,7 +265,8 @@
      */
     @Override
     public int hashCode() {
-        return HashCodeHelpers.hashCode(mOutputConfigurations.hashCode(), mInputConfig.hashCode(),
+        return HashCodeHelpers.hashCode(mOutputConfigurations.hashCode(),
+                Objects.hashCode(mInputConfig),
                 mSessionType);
     }
 
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 4d176f2..bb3f6c9 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -277,13 +277,3 @@
     purpose: PURPOSE_BUGFIX
   }
 }
-
-flag {
-  name: "typeface_cache_for_var_settings"
-  namespace: "text"
-  description: "Cache Typeface instance for font variation settings."
-  bug: "355462362"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
\ No newline at end of file
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index a7641c0..9e4b27d 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -3574,7 +3574,7 @@
             checkPreconditions(sc);
             if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
                 SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
-                        "reparent", this, sc,
+                        "setColor", this, sc,
                         "r=" + color[0] + " g=" + color[1] + " b=" + color[2]);
             }
             nativeSetColor(mNativeObject, sc.mNativeObject, color);
diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java
index f72a5ca..f210741 100644
--- a/core/java/com/android/internal/graphics/ColorUtils.java
+++ b/core/java/com/android/internal/graphics/ColorUtils.java
@@ -21,7 +21,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.graphics.Color;
-
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import com.android.internal.graphics.cam.Cam;
 
 /**
@@ -29,6 +29,7 @@
  *
  * A set of color-related utility methods, building upon those available in {@code Color}.
  */
+@RavenwoodKeepWholeClass
 public final class ColorUtils {
 
     private static final double XYZ_WHITE_REFERENCE_X = 95.047;
@@ -696,4 +697,4 @@
         double calculateContrast(int foreground, int background, int alpha);
     }
 
-}
\ No newline at end of file
+}
diff --git a/core/java/com/android/internal/graphics/cam/Cam.java b/core/java/com/android/internal/graphics/cam/Cam.java
index 1df85c3..49fa37b 100644
--- a/core/java/com/android/internal/graphics/cam/Cam.java
+++ b/core/java/com/android/internal/graphics/cam/Cam.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 
 import com.android.internal.graphics.ColorUtils;
 
@@ -25,6 +26,7 @@
  * A color appearance model, based on CAM16, extended to use L* as the lightness dimension, and
  * coupled to a gamut mapping algorithm. Creates a color system, enables a digital design system.
  */
+@RavenwoodKeepWholeClass
 public class Cam {
     // The maximum difference between the requested L* and the L* returned.
     private static final float DL_MAX = 0.2f;
diff --git a/core/java/com/android/internal/graphics/cam/CamUtils.java b/core/java/com/android/internal/graphics/cam/CamUtils.java
index f541729..76fabc6 100644
--- a/core/java/com/android/internal/graphics/cam/CamUtils.java
+++ b/core/java/com/android/internal/graphics/cam/CamUtils.java
@@ -19,6 +19,7 @@
 
 import android.annotation.NonNull;
 import android.graphics.Color;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 
 import com.android.internal.graphics.ColorUtils;
 
@@ -45,6 +46,7 @@
  * consistent, and reasonably good. It worked." - Fairchild, Color Models and Systems: Handbook of
  * Color Psychology, 2015
  */
+@RavenwoodKeepWholeClass
 public final class CamUtils {
     private CamUtils() {
     }
diff --git a/core/java/com/android/internal/graphics/cam/Frame.java b/core/java/com/android/internal/graphics/cam/Frame.java
index 0ac7cbc..c419fab 100644
--- a/core/java/com/android/internal/graphics/cam/Frame.java
+++ b/core/java/com/android/internal/graphics/cam/Frame.java
@@ -17,6 +17,7 @@
 package com.android.internal.graphics.cam;
 
 import android.annotation.NonNull;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.util.MathUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -33,6 +34,7 @@
  * number of calculations during the color => CAM conversion process that depend only on the viewing
  * conditions. Caching those calculations in a Frame instance saves a significant amount of time.
  */
+@RavenwoodKeepWholeClass
 public final class Frame {
     // Standard viewing conditions assumed in RGB specification - Stokes, Anderson, Chandrasekar,
     // Motta - A Standard Default Color Space for the Internet: sRGB, 1996.
diff --git a/core/java/com/android/internal/graphics/cam/HctSolver.java b/core/java/com/android/internal/graphics/cam/HctSolver.java
index d7a8691..6e558e7 100644
--- a/core/java/com/android/internal/graphics/cam/HctSolver.java
+++ b/core/java/com/android/internal/graphics/cam/HctSolver.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.graphics.cam;
 
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+
 /**
  * An efficient algorithm for determining the closest sRGB color to a set of HCT coordinates,
  * based on geometrical insights for finding intersections in linear RGB, CAM16, and L*a*b*.
@@ -24,6 +26,7 @@
  * Copied from //java/com/google/ux/material/libmonet/hct on May 22 2022.
  * ColorUtils/MathUtils functions that were required were added to CamUtils.
  */
+@RavenwoodKeepWholeClass
 public class HctSolver {
     private HctSolver() {}
 
diff --git a/core/res/res/layout/time_picker_text_input_material.xml b/core/res/res/layout/time_picker_text_input_material.xml
index 4988842..86070b1 100644
--- a/core/res/res/layout/time_picker_text_input_material.xml
+++ b/core/res/res/layout/time_picker_text_input_material.xml
@@ -34,19 +34,29 @@
         android:layoutDirection="ltr">
         <EditText
             android:id="@+id/input_hour"
-            android:layout_width="50dp"
+            android:layout_width="50sp"
             android:layout_height="wrap_content"
+            android:layout_alignEnd="@id/hour_label_holder"
             android:inputType="number"
             android:textAppearance="@style/TextAppearance.Material.TimePicker.InputField"
             android:imeOptions="actionNext"/>
-        <TextView
-            android:id="@+id/label_hour"
+        <!-- Ensure the label_hour takes up at least 50sp of space -->
+        <FrameLayout
+            android:id="@+id/hour_label_holder"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_below="@id/input_hour"
-            android:layout_alignStart="@id/input_hour"
-            android:labelFor="@+id/input_hour"
-            android:text="@string/time_picker_hour_label"/>
+            android:layout_below="@id/input_hour">
+            <TextView
+                android:id="@+id/label_hour"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:labelFor="@+id/input_hour"
+                android:text="@string/time_picker_hour_label"/>
+            <Space
+                android:layout_width="50sp"
+                android:layout_height="0dp"/>
+        </FrameLayout>
 
         <TextView
             android:id="@+id/input_separator"
@@ -58,21 +68,30 @@
 
         <EditText
             android:id="@+id/input_minute"
-            android:layout_width="50dp"
+            android:layout_width="50sp"
             android:layout_height="wrap_content"
             android:layout_alignBaseline="@id/input_hour"
             android:layout_toEndOf="@id/input_separator"
             android:inputType="number"
             android:textAppearance="@style/TextAppearance.Material.TimePicker.InputField" />
-        <TextView
-            android:id="@+id/label_minute"
+        <!-- Ensure the label_minute takes up at least 50sp of space -->
+        <FrameLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_below="@id/input_minute"
             android:layout_alignStart="@id/input_minute"
-            android:labelFor="@+id/input_minute"
-            android:text="@string/time_picker_minute_label"/>
-
+            >
+            <TextView
+                android:id="@+id/label_minute"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:labelFor="@+id/input_minute"
+                android:text="@string/time_picker_minute_label"/>
+            <Space
+                android:layout_width="50sp"
+                android:layout_height="0dp"/>
+        </FrameLayout>
         <TextView
             android:visibility="invisible"
             android:id="@+id/label_error"
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 5793bbe..2bbaf9c 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -249,6 +249,7 @@
     ],
     srcs: [
         "src/android/app/ActivityManagerTest.java",
+        "src/android/colormodel/CamTest.java",
         "src/android/content/ContextTest.java",
         "src/android/content/pm/PackageManagerTest.java",
         "src/android/content/pm/UserInfoTest.java",
diff --git a/core/tests/coretests/src/android/colormodel/CamTest.java b/core/tests/coretests/src/android/colormodel/CamTest.java
index 05fc0e0..cf398db 100644
--- a/core/tests/coretests/src/android/colormodel/CamTest.java
+++ b/core/tests/coretests/src/android/colormodel/CamTest.java
@@ -18,9 +18,12 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.filters.LargeTest;
 
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -35,6 +38,9 @@
     static final int GREEN = 0xff00ff00;
     static final int BLUE = 0xff0000ff;
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     @Test
     public void camFromIntToInt() {
         Cam cam = Cam.fromInt(RED);
diff --git a/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
deleted file mode 100644
index 8a54e5b..0000000
--- a/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.test.InstrumentationTestCase;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.text.flags.Flags;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * PaintTest tests {@link Paint}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class PaintFontVariationTest extends InstrumentationTestCase {
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
-    @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
-    @Test
-    public void testDerivedFromSameTypeface() {
-        final Paint p = new Paint();
-
-        p.setTypeface(Typeface.SANS_SERIF);
-        assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
-        Typeface first = p.getTypeface();
-
-        p.setTypeface(Typeface.SANS_SERIF);
-        assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
-        Typeface second = p.getTypeface();
-
-        assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
-    }
-
-    @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
-    @Test
-    public void testDerivedFromChained() {
-        final Paint p = new Paint();
-
-        p.setTypeface(Typeface.SANS_SERIF);
-        assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
-        Typeface first = p.getTypeface();
-
-        assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
-        Typeface second = p.getTypeface();
-
-        assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
-    }
-}
diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java
index 878ba70..0dec756 100644
--- a/core/tests/coretests/src/android/graphics/PaintTest.java
+++ b/core/tests/coretests/src/android/graphics/PaintTest.java
@@ -16,22 +16,13 @@
 
 package android.graphics;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.junit.Assert.assertNotEquals;
 
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.test.InstrumentationTestCase;
 import android.text.TextUtils;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.text.flags.Flags;
-
-import org.junit.Rule;
-
 import java.util.Arrays;
 import java.util.HashSet;
 
@@ -39,9 +30,6 @@
  * PaintTest tests {@link Paint}.
  */
 public class PaintTest extends InstrumentationTestCase {
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
     private static final String FONT_PATH = "fonts/HintedAdvanceWidthTest-Regular.ttf";
 
     static void assertEquals(String message, float[] expected, float[] actual) {
@@ -415,33 +403,4 @@
         assertEquals(6, getClusterCount(p, rtlStr + ltrStr));
         assertEquals(9, getClusterCount(p, ltrStr + rtlStr + ltrStr));
     }
-
-    @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
-    public void testDerivedFromSameTypeface() {
-        final Paint p = new Paint();
-
-        p.setTypeface(Typeface.SANS_SERIF);
-        assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
-        Typeface first = p.getTypeface();
-
-        p.setTypeface(Typeface.SANS_SERIF);
-        assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
-        Typeface second = p.getTypeface();
-
-        assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
-    }
-
-    @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
-    public void testDerivedFromChained() {
-        final Paint p = new Paint();
-
-        p.setTypeface(Typeface.SANS_SERIF);
-        assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
-        Typeface first = p.getTypeface();
-
-        assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
-        Typeface second = p.getTypeface();
-
-        assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
-    }
 }
diff --git a/core/tests/resourceflaggingtests/Android.bp b/core/tests/resourceflaggingtests/Android.bp
index dd86094..efb8437 100644
--- a/core/tests/resourceflaggingtests/Android.bp
+++ b/core/tests/resourceflaggingtests/Android.bp
@@ -22,54 +22,6 @@
     default_team: "trendy_team_android_resources",
 }
 
-genrule {
-    name: "resource-flagging-test-app-resources-compile",
-    tools: ["aapt2"],
-    srcs: [
-        "flagged_resources_res/values/bools.xml",
-    ],
-    out: ["values_bools.arsc.flat"],
-    cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
-        "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
-}
-
-genrule {
-    name: "resource-flagging-test-app-resources-compile2",
-    tools: ["aapt2"],
-    srcs: [
-        "flagged_resources_res/values/bools2.xml",
-    ],
-    out: ["values_bools2.arsc.flat"],
-    cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
-        "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
-}
-
-genrule {
-    name: "resource-flagging-test-app-apk",
-    tools: ["aapt2"],
-    // The first input file in the list must be the manifest
-    srcs: [
-        "TestAppAndroidManifest.xml",
-        ":resource-flagging-test-app-resources-compile",
-        ":resource-flagging-test-app-resources-compile2",
-    ],
-    out: ["resapp.apk"],
-    cmd: "$(location aapt2) link -o $(out) --manifest $(in)",
-}
-
-java_genrule {
-    name: "resource-flagging-apk-as-resource",
-    srcs: [
-        ":resource-flagging-test-app-apk",
-    ],
-    out: ["apks_as_resources.res.zip"],
-    tools: ["soong_zip"],
-
-    cmd: "mkdir -p $(genDir)/res/raw && " +
-        "cp $(in) $(genDir)/res/raw/$$(basename $(in)) && " +
-        "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res",
-}
-
 android_test {
     name: "ResourceFlaggingTests",
     srcs: [
@@ -82,6 +34,6 @@
         "testng",
         "compatibility-device-util-axt",
     ],
-    resource_zips: [":resource-flagging-apk-as-resource"],
+    resource_zips: [":resource-flagging-test-app-apk-as-resource"],
     test_suites: ["device-tests"],
 }
diff --git a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
index ad8542e..c1e3578 100644
--- a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
+++ b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
@@ -69,11 +69,23 @@
     }
 
     private boolean getBoolean(String name) {
-        int resId = mResources.getIdentifier(name, "bool", "com.android.intenal.flaggedresources");
+        int resId = mResources.getIdentifier(
+                name,
+                "bool",
+                "com.android.intenal.flaggedresources");
         assertThat(resId).isNotEqualTo(0);
         return mResources.getBoolean(resId);
     }
 
+    private String getString(String name) {
+        int resId = mResources.getIdentifier(
+                name,
+                "string",
+                "com.android.intenal.flaggedresources");
+        assertThat(resId).isNotEqualTo(0);
+        return mResources.getString(resId);
+    }
+
     private String extractApkAndGetPath(int id) throws Exception {
         final Resources resources = mContext.getResources();
         try (InputStream is = resources.openRawResource(id)) {
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 889a7785..fd78816 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -56,7 +56,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
-import com.android.text.flags.Flags;
 
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
@@ -75,7 +74,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -145,23 +143,6 @@
     private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
     private static final Object sDynamicCacheLock = new Object();
 
-    private static final LruCache<Long, LruCache<String, Typeface>> sVariableCache =
-            new LruCache<>(16);
-    private static final Object sVariableCacheLock = new Object();
-
-    /** @hide */
-    @VisibleForTesting
-    public static void clearTypefaceCachesForTestingPurpose() {
-        synchronized (sWeightCacheLock) {
-            sWeightTypefaceCache.clear();
-        }
-        synchronized (sDynamicCacheLock) {
-            sDynamicTypefaceCache.evictAll();
-        }
-        synchronized (sVariableCacheLock) {
-            sVariableCache.evictAll();
-        }
-    }
 
     @GuardedBy("SYSTEM_FONT_MAP_LOCK")
     static Typeface sDefaultTypeface;
@@ -214,8 +195,6 @@
     @UnsupportedAppUsage
     public final long native_instance;
 
-    private final Typeface mDerivedFrom;
-
     private final String mSystemFontFamilyName;
 
     private final Runnable mCleaner;
@@ -295,18 +274,6 @@
     }
 
     /**
-     * Returns the Typeface used for creating this Typeface.
-     *
-     * Maybe null if this is not derived from other Typeface.
-     * TODO(b/357707916): Make this public API.
-     * @hide
-     */
-    @VisibleForTesting
-    public final @Nullable Typeface getDerivedFrom() {
-        return mDerivedFrom;
-    }
-
-    /**
      * Returns the system font family name if the typeface was created from a system font family,
      * otherwise returns null.
      */
@@ -1054,51 +1021,9 @@
         return typeface;
     }
 
-    private static String axesToVarKey(@NonNull List<FontVariationAxis> axes) {
-        // The given list can be mutated because it is allocated in Paint#setFontVariationSettings.
-        // Currently, Paint#setFontVariationSettings is the only code path reaches this method.
-        axes.sort(Comparator.comparingInt(FontVariationAxis::getOpenTypeTagValue));
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < axes.size(); ++i) {
-            final FontVariationAxis fva = axes.get(i);
-            sb.append(fva.getTag());
-            sb.append(fva.getStyleValue());
-        }
-        return sb.toString();
-    }
-
-    /**
-     * TODO(b/357707916): Make this public API.
-     * @hide
-     */
+    /** @hide */
     public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
             @NonNull List<FontVariationAxis> axes) {
-        if (Flags.typefaceCacheForVarSettings()) {
-            final Typeface target = (family == null) ? Typeface.DEFAULT : family;
-            final Typeface base = (target.mDerivedFrom == null) ? target : target.mDerivedFrom;
-
-            final String key = axesToVarKey(axes);
-
-            synchronized (sVariableCacheLock) {
-                LruCache<String, Typeface> innerCache = sVariableCache.get(base.native_instance);
-                if (innerCache == null) {
-                    // Cache up to 16 var instance per root Typeface
-                    innerCache = new LruCache<>(16);
-                    sVariableCache.put(base.native_instance, innerCache);
-                } else {
-                    Typeface cached = innerCache.get(key);
-                    if (cached != null) {
-                        return cached;
-                    }
-                }
-                Typeface typeface = new Typeface(
-                        nativeCreateFromTypefaceWithVariation(base.native_instance, axes),
-                        base.getSystemFontFamilyName(), base);
-                innerCache.put(key, typeface);
-                return typeface;
-            }
-        }
-
         final Typeface base = family == null ? Typeface.DEFAULT : family;
         Typeface typeface = new Typeface(
                 nativeCreateFromTypefaceWithVariation(base.native_instance, axes),
@@ -1259,19 +1184,11 @@
     // don't allow clients to call this directly
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private Typeface(long ni) {
-        this(ni, null, null);
+        this(ni, null);
     }
 
-
     // don't allow clients to call this directly
-    // This is kept for robolectric.
     private Typeface(long ni, @Nullable String systemFontFamilyName) {
-        this(ni, systemFontFamilyName, null);
-    }
-
-    // don't allow clients to call this directly
-    private Typeface(long ni, @Nullable String systemFontFamilyName,
-            @Nullable Typeface derivedFrom) {
         if (ni == 0) {
             throw new RuntimeException("native typeface cannot be made");
         }
@@ -1281,7 +1198,6 @@
         mStyle = nativeGetStyle(ni);
         mWeight = nativeGetWeight(ni);
         mSystemFontFamilyName = systemFontFamilyName;
-        mDerivedFrom = derivedFrom;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 0a8166f..df5f1e4 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -139,6 +139,10 @@
     <string name="bubble_accessibility_action_move_bottom_left">Move bottom left</string>
     <!-- Action in accessibility menu to move the stack of bubbles to the bottom right of the screen. [CHAR LIMIT=30]-->
     <string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string>
+    <!-- Click action label for bubbles to expand menu. [CHAR LIMIT=30]-->
+    <string name="bubble_accessibility_action_expand_menu">expand menu</string>
+    <!-- Click action label for bubbles to collapse menu. [CHAR LIMIT=30]-->
+    <string name="bubble_accessibility_action_collapse_menu">collapse menu</string>
     <!-- Accessibility announcement when the stack of bubbles expands. [CHAR LIMIT=NONE]-->
     <string name="bubble_accessibility_announce_expand">expand <xliff:g id="bubble_title" example="Messages">%1$s</xliff:g></string>
     <!-- Accessibility announcement when the stack of bubbles collapses. [CHAR LIMIT=NONE]-->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index c374478..b7834db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -32,6 +32,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 
 import com.android.wm.shell.R;
@@ -188,6 +189,17 @@
 
             // Handle view needs to draw on top of task view.
             bringChildToFront(mHandleView);
+
+            mHandleView.setAccessibilityDelegate(new AccessibilityDelegate() {
+                @Override
+                public void onInitializeAccessibilityNodeInfo(View host,
+                        AccessibilityNodeInfo info) {
+                    super.onInitializeAccessibilityNodeInfo(host, info);
+                    info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
+                            AccessibilityNodeInfo.ACTION_CLICK, getResources().getString(
+                            R.string.bubble_accessibility_action_expand_menu)));
+                }
+            });
         }
         mMenuViewController = new BubbleBarMenuViewController(mContext, this);
         mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
index 8389c81..0300869 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -23,7 +23,9 @@
 import android.graphics.drawable.Icon;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
+import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
@@ -71,6 +73,16 @@
         mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title);
         mBubbleDismissIconView = findViewById(R.id.bubble_bar_manage_menu_dismiss_icon);
         updateThemeColors();
+
+        mBubbleSectionView.setAccessibilityDelegate(new AccessibilityDelegate() {
+            @Override
+            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+                super.onInitializeAccessibilityNodeInfo(host, info);
+                info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
+                        AccessibilityNodeInfo.ACTION_CLICK, getResources().getString(
+                        R.string.bubble_accessibility_action_collapse_menu)));
+            }
+        });
     }
 
     private void updateThemeColors() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 7774384..dc21f82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -31,6 +31,8 @@
 import android.os.Bundle;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
+import android.window.DisplayAreaInfo;
+import android.window.WindowContainerTransaction;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.Nullable;
@@ -40,6 +42,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
@@ -71,7 +74,8 @@
  */
 public class PipController implements ConfigurationChangeListener,
         PipTransitionState.PipTransitionStateChangedListener,
-        DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> {
+        DisplayController.OnDisplaysChangedListener,
+        DisplayChangeController.OnDisplayChangingListener, RemoteCallable<PipController> {
     private static final String TAG = PipController.class.getSimpleName();
     private static final String SWIPE_TO_PIP_APP_BOUNDS = "pip_app_bounds";
     private static final String SWIPE_TO_PIP_OVERLAY = "swipe_to_pip_overlay";
@@ -197,11 +201,12 @@
         mPipDisplayLayoutState.setDisplayLayout(layout);
 
         mDisplayController.addDisplayWindowListener(this);
+        mDisplayController.addDisplayChangingController(this);
         mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
                 new DisplayInsetsController.OnInsetsChangedListener() {
                     @Override
                     public void insetsChanged(InsetsState insetsState) {
-                        onDisplayChanged(mDisplayController
+                        setDisplayLayout(mDisplayController
                                         .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
                     }
                 });
@@ -264,11 +269,12 @@
 
     @Override
     public void onThemeChanged() {
-        onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()));
+        setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay()));
     }
 
     //
-    // DisplayController.OnDisplaysChangedListener implementations
+    // DisplayController.OnDisplaysChangedListener and
+    // DisplayChangeController.OnDisplayChangingListener implementations
     //
 
     @Override
@@ -276,7 +282,7 @@
         if (displayId != mPipDisplayLayoutState.getDisplayId()) {
             return;
         }
-        onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+        setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
     }
 
     @Override
@@ -284,10 +290,35 @@
         if (displayId != mPipDisplayLayoutState.getDisplayId()) {
             return;
         }
-        onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+        setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
     }
 
-    private void onDisplayChanged(DisplayLayout layout) {
+    /**
+     * A callback for any observed transition that contains a display change in its
+     * {@link android.window.TransitionRequestInfo} with a non-zero rotation delta.
+     */
+    @Override
+    public void onDisplayChange(int displayId, int fromRotation, int toRotation,
+            @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction t) {
+        if (!mPipTransitionState.isInPip()) {
+            return;
+        }
+
+        // Calculate the snap fraction pre-rotation.
+        float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds());
+
+        // Update the caches to reflect the new display layout and movement bounds.
+        mPipDisplayLayoutState.rotateTo(toRotation);
+        mPipTouchHandler.updateMovementBounds();
+
+        // The policy is to keep PiP width, height and snap fraction invariant.
+        Rect toBounds = mPipBoundsState.getBounds();
+        mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction);
+        mPipBoundsState.setBounds(toBounds);
+        t.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
+    }
+
+    private void setDisplayLayout(DisplayLayout layout) {
         mPipDisplayLayoutState.setDisplayLayout(layout);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index d7c225b..d75fa00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -1081,7 +1081,7 @@
      * Updates the current movement bounds based on whether the menu is currently visible and
      * resized.
      */
-    private void updateMovementBounds() {
+    void updateMovementBounds() {
         Rect insetBounds = new Rect();
         mPipBoundsAlgorithm.getInsetBounds(insetBounds);
         mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 48d17ec6..c11a112 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -439,9 +439,9 @@
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setResizeTransition: hasPendingResize=%b",
                 mPendingResize != null);
         if (mPendingResize != null) {
+            mPendingResize.cancel(null);
             mainDecor.cancelRunningAnimations();
             sideDecor.cancelRunningAnimations();
-            mPendingResize.cancel(null);
             mAnimations.clear();
             onFinish(null /* wct */);
         }
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8751596..a5fd5b9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -971,8 +971,8 @@
     <string name="hearing_devices_presets_error">Couldn\'t update preset</string>
     <!-- QuickSettings: Title for hearing aids presets. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]-->
     <string name="hearing_devices_preset_label">Preset</string>
-    <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40]-->
-    <string name="live_caption_title">Live Caption</string>
+    <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40] [BACKUP_MESSAGE_ID=8916875614623730005]-->
+    <string name="quick_settings_hearing_devices_live_caption_title">Live Caption</string>
 
     <!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] -->
     <string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string>
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 083f1db..b6fe0df 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -499,7 +499,8 @@
         final List<ResolveInfo> resolved = packageManager.queryIntentActivities(LIVE_CAPTION_INTENT,
                 /* flags= */ 0);
         if (!resolved.isEmpty()) {
-            return new ToolItem(context.getString(R.string.live_caption_title),
+            return new ToolItem(
+                    context.getString(R.string.quick_settings_hearing_devices_live_caption_title),
                     context.getDrawable(R.drawable.ic_volume_odi_captions),
                     LIVE_CAPTION_INTENT);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 51d92f0..5dc020f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -300,7 +300,9 @@
                 swipeToDismissInteractor.dismissFling
                     .filterNotNull()
                     .filterRelevantKeyguardState()
-                    .collect { _ -> startTransitionTo(KeyguardState.GONE) }
+                    .collect { _ ->
+                        startTransitionTo(KeyguardState.GONE, ownerReason = "dismissFling != null")
+                    }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
index 86e4115..906d586 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
@@ -19,14 +19,15 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.util.kotlin.Utils.Companion.sample
-import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import javax.inject.Inject
 
 /**
  * Handles logic around the swipe to dismiss gesture, where the user swipes up on the dismissable
@@ -53,12 +54,14 @@
         shadeRepository.currentFling
             .sample(
                 transitionInteractor.startedKeyguardState,
-                keyguardInteractor.isKeyguardDismissible
+                keyguardInteractor.isKeyguardDismissible,
+                keyguardInteractor.statusBarState,
             )
-            .filter { (flingInfo, startedState, keyguardDismissable) ->
+            .filter { (flingInfo, startedState, keyguardDismissable, statusBarState) ->
                 flingInfo != null &&
-                    !flingInfo.expand &&
-                    startedState == KeyguardState.LOCKSCREEN &&
+                        !flingInfo.expand &&
+                        statusBarState != StatusBarState.SHADE_LOCKED &&
+                        startedState == KeyguardState.LOCKSCREEN &&
                     keyguardDismissable
             }
             .map { (flingInfo, _) -> flingInfo }
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 98a61df..863a899 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -24,6 +24,7 @@
 import android.net.Uri
 import android.os.Handler
 import android.os.UserHandle
+import android.util.Log
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.dagger.qualifiers.LongRunning
@@ -71,6 +72,7 @@
     override fun provideRecordingServiceStrings(): RecordingServiceStrings = IrsStrings(resources)
 
     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        Log.d(getTag(), "handling action: ${intent?.action}")
         when (intent?.action) {
             ACTION_START -> {
                 bgExecutor.execute {
@@ -95,7 +97,7 @@
                 bgExecutor.execute {
                     mNotificationManager.cancelAsUser(
                         null,
-                        mNotificationId,
+                        intent.getIntExtra(EXTRA_NOTIFICATION_ID, mNotificationId),
                         UserHandle(mUserContextTracker.userContext.userId)
                     )
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 108564c..700253ba 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -80,6 +80,7 @@
             "com.android.systemui.screenrecord.STOP_FROM_NOTIF";
     protected static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
     private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+    protected static final String EXTRA_NOTIFICATION_ID = "notification_id";
 
     private final RecordingController mController;
     protected final KeyguardDismissUtil mKeyguardDismissUtil;
@@ -542,7 +543,8 @@
 
     private Intent getShareIntent(Context context, Uri path) {
         return new Intent(context, this.getClass()).setAction(ACTION_SHARE)
-                .putExtra(EXTRA_PATH, path);
+                .putExtra(EXTRA_PATH, path)
+                .putExtra(EXTRA_NOTIFICATION_ID, mNotificationId);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index b6d58d6..db8cd57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2985,7 +2985,7 @@
                 @Override
                 public void onFalse() {
                     // Hides quick settings, bouncer, and quick-quick settings.
-                    mStatusBarKeyguardViewManager.reset(true);
+                    mStatusBarKeyguardViewManager.reset(true, /* isFalsingReset= */true);
                 }
             };
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 2d775b7..f8f9b77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -708,7 +708,7 @@
      * Shows the notification keyguard or the bouncer depending on
      * {@link #needsFullscreenBouncer()}.
      */
-    protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
+    protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing, boolean isFalsingReset) {
         boolean isDozing = mDozing;
         if (Flags.simPinRaceConditionOnRestart()) {
             KeyguardState toState = mKeyguardTransitionInteractor.getTransitionState().getValue()
@@ -734,8 +734,11 @@
                         mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
                     }
                 }
-            } else {
-                Log.e(TAG, "Attempted to show the sim bouncer when it is already showing.");
+            } else if (!isFalsingReset) {
+                // Falsing resets can cause this to flicker, so don't reset in this case
+                Log.i(TAG, "Sim bouncer is already showing, issuing a refresh");
+                mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+
             }
         } else {
             mCentralSurfaces.showKeyguard();
@@ -957,6 +960,10 @@
 
     @Override
     public void reset(boolean hideBouncerWhenShowing) {
+        reset(hideBouncerWhenShowing, /* isFalsingReset= */false);
+    }
+
+    public void reset(boolean hideBouncerWhenShowing, boolean isFalsingReset) {
         if (mKeyguardStateController.isShowing() && !bouncerIsAnimatingAway()) {
             final boolean isOccluded = mKeyguardStateController.isOccluded();
             // Hide quick settings.
@@ -968,7 +975,7 @@
                     hideBouncer(false /* destroyView */);
                 }
             } else {
-                showBouncerOrKeyguard(hideBouncerWhenShowing);
+                showBouncerOrKeyguard(hideBouncerWhenShowing, isFalsingReset);
             }
             if (hideBouncerWhenShowing) {
                 hideAlternateBouncer(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index af5e60e..9b61105 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -1068,7 +1068,7 @@
     public void testShowBouncerOrKeyguard_needsFullScreen() {
         when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
                 KeyguardSecurityModel.SecurityMode.SimPin);
-        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false);
         verify(mCentralSurfaces).hideKeyguard();
         verify(mPrimaryBouncerInteractor).show(true);
     }
@@ -1084,7 +1084,7 @@
                 .thenReturn(KeyguardState.LOCKSCREEN);
 
         reset(mCentralSurfaces);
-        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false);
         verify(mPrimaryBouncerInteractor).show(true);
         verify(mCentralSurfaces).showKeyguard();
     }
@@ -1092,11 +1092,26 @@
     @Test
     @DisableSceneContainer
     public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing() {
+        boolean isFalsingReset = false;
         when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
                 KeyguardSecurityModel.SecurityMode.SimPin);
         when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
-        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset);
         verify(mCentralSurfaces, never()).hideKeyguard();
+        verify(mPrimaryBouncerInteractor).show(true);
+    }
+
+    @Test
+    @DisableSceneContainer
+    public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing_onFalsing() {
+        boolean isFalsingReset = true;
+        when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+                KeyguardSecurityModel.SecurityMode.SimPin);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
+        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset);
+        verify(mCentralSurfaces, never()).hideKeyguard();
+
+        // Do not refresh the full screen bouncer if the call is from falsing
         verify(mPrimaryBouncerInteractor, never()).show(true);
     }
 
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index 68f185e..cc9b70e 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -281,6 +281,12 @@
 
 com.android.server.LocalServices
 
+com.android.internal.graphics.cam.Cam
+com.android.internal.graphics.cam.CamUtils
+com.android.internal.graphics.cam.Frame
+com.android.internal.graphics.cam.HctSolver
+com.android.internal.graphics.ColorUtils
+
 com.android.internal.util.BitUtils
 com.android.internal.util.BitwiseInputStream
 com.android.internal.util.BitwiseOutputStream
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 4dbbfa2..c77b768 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -512,6 +512,9 @@
         /**
          * Gets the next input method and subtype from the given ones.
          *
+         * <p>If the given input method and subtype are not found, this returns the most recent
+         * input method and subtype.</p>
+         *
          * @param imi            the input method to find the next value from.
          * @param subtype        the input method subtype to find the next value from, if any.
          * @param onlyCurrentIme whether to consider only subtypes of the current input method.
@@ -523,17 +526,20 @@
         public ImeSubtypeListItem next(@NonNull InputMethodInfo imi,
                 @Nullable InputMethodSubtype subtype, boolean onlyCurrentIme,
                 boolean useRecency, boolean forward) {
-            final int size = mItems.size();
-            if (size <= 1) {
+            if (mItems.isEmpty()) {
                 return null;
             }
             final int index = getIndex(imi, subtype, useRecency);
             if (index < 0) {
-                return null;
+                Slog.w(TAG, "Trying to switch away from input method: " + imi
+                        + " and subtype " + subtype + " which are not in the list,"
+                        + " falling back to most recent item in list.");
+                return mItems.get(mRecencyMap[0]);
             }
 
             final int incrementSign = (forward ? 1 : -1);
 
+            final int size = mItems.size();
             for (int i = 1; i < size; i++) {
                 final int nextIndex = (index + i * incrementSign + size) % size;
                 final int mappedIndex = useRecency ? mRecencyMap[nextIndex] : nextIndex;
@@ -554,7 +560,7 @@
          */
         public boolean setMostRecent(@NonNull InputMethodInfo imi,
                 @Nullable InputMethodSubtype subtype) {
-            if (mItems.size() <= 1) {
+            if (mItems.isEmpty()) {
                 return false;
             }
 
@@ -849,6 +855,9 @@
     /**
      * Gets the next input method and subtype, starting from the given ones, in the given direction.
      *
+     * <p>If the given input method and subtype are not found, this returns the most recent
+     * input method and subtype.</p>
+     *
      * @param onlyCurrentIme whether to consider only subtypes of the current input method.
      * @param imi            the input method to find the next value from.
      * @param subtype        the input method subtype to find the next value from, if any.
@@ -867,6 +876,9 @@
      * Gets the next input method and subtype suitable for hardware keyboards, starting from the
      * given ones, in the given direction.
      *
+     * <p>If the given input method and subtype are not found, this returns the most recent
+     * input method and subtype.</p>
+     *
      * @param onlyCurrentIme whether to consider only subtypes of the current input method.
      * @param imi            the input method to find the next value from.
      * @param subtype        the input method subtype to find the next value from, if any.
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 17f6561..a6f4c0e 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -530,6 +530,12 @@
      */
     private boolean mUseDifferentDelaysForBackgroundChain;
 
+    /**
+     * Core uids and apps without the internet permission will not have any firewall rules applied
+     * to them.
+     */
+    private boolean mNeverApplyRulesToCoreUids;
+
     // See main javadoc for instructions on how to use these locks.
     final Object mUidRulesFirstLock = new Object();
     final Object mNetworkPoliciesSecondLock = new Object();
@@ -760,7 +766,8 @@
 
     /** List of apps indexed by uid and whether they have the internet permission */
     @GuardedBy("mUidRulesFirstLock")
-    private final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray();
+    @VisibleForTesting
+    final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray();
 
     /**
      * Map of uid -> UidStateCallbackInfo objects holding the data received from
@@ -1038,6 +1045,7 @@
 
             mUseMeteredFirewallChains = Flags.useMeteredFirewallChains();
             mUseDifferentDelaysForBackgroundChain = Flags.useDifferentDelaysForBackgroundChain();
+            mNeverApplyRulesToCoreUids = Flags.neverApplyRulesToCoreUids();
 
             synchronized (mUidRulesFirstLock) {
                 synchronized (mNetworkPoliciesSecondLock) {
@@ -4088,6 +4096,8 @@
                         + mUseMeteredFirewallChains);
                 fout.println(Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN + ": "
                         + mUseDifferentDelaysForBackgroundChain);
+                fout.println(Flags.FLAG_NEVER_APPLY_RULES_TO_CORE_UIDS + ": "
+                        + mNeverApplyRulesToCoreUids);
 
                 fout.println();
                 fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode);
@@ -4878,6 +4888,12 @@
                 int[] idleUids = mUsageStats.getIdleUidsForUser(user.id);
                 for (int uid : idleUids) {
                     if (!mPowerSaveTempWhitelistAppIds.get(UserHandle.getAppId(uid), false)) {
+                        if (mNeverApplyRulesToCoreUids && !isUidValidForRulesUL(uid)) {
+                            // This check is needed to keep mUidFirewallStandbyRules free of any
+                            // such uids. Doing this keeps it in sync with the actual rules applied
+                            // in the underlying connectivity stack.
+                            continue;
+                        }
                         // quick check: if this uid doesn't have INTERNET permission, it
                         // doesn't have network access anyway, so it is a waste to mess
                         // with it here.
@@ -5180,6 +5196,11 @@
 
     @GuardedBy("mUidRulesFirstLock")
     private boolean isUidValidForAllowlistRulesUL(int uid) {
+        return isUidValidForRulesUL(uid);
+    }
+
+    @GuardedBy("mUidRulesFirstLock")
+    private boolean isUidValidForRulesUL(int uid) {
         return UserHandle.isApp(uid) && hasInternetPermissionUL(uid);
     }
 
@@ -6194,41 +6215,33 @@
         }
     }
 
-    private void addSdkSandboxUidsIfNeeded(SparseIntArray uidRules) {
-        final int size = uidRules.size();
-        final SparseIntArray sdkSandboxUids = new SparseIntArray();
-        for (int index = 0; index < size; index++) {
-            final int uid = uidRules.keyAt(index);
-            final int rule = uidRules.valueAt(index);
-            if (Process.isApplicationUid(uid)) {
-                sdkSandboxUids.put(Process.toSdkSandboxUid(uid), rule);
-            }
-        }
-
-        for (int index = 0; index < sdkSandboxUids.size(); index++) {
-            final int uid = sdkSandboxUids.keyAt(index);
-            final int rule = sdkSandboxUids.valueAt(index);
-            uidRules.put(uid, rule);
-        }
-    }
-
     /**
      * Set uid rules on a particular firewall chain. This is going to synchronize the rules given
      * here to netd.  It will clean up dead rules and make sure the target chain only contains rules
      * specified here.
      */
+    @GuardedBy("mUidRulesFirstLock")
     private void setUidFirewallRulesUL(int chain, SparseIntArray uidRules) {
-        addSdkSandboxUidsIfNeeded(uidRules);
         try {
             int size = uidRules.size();
-            int[] uids = new int[size];
-            int[] rules = new int[size];
+            final IntArray uids = new IntArray(size);
+            final IntArray rules = new IntArray(size);
             for(int index = size - 1; index >= 0; --index) {
-                uids[index] = uidRules.keyAt(index);
-                rules[index] = uidRules.valueAt(index);
+                final int uid = uidRules.keyAt(index);
+                if (mNeverApplyRulesToCoreUids && !isUidValidForRulesUL(uid)) {
+                    continue;
+                }
+                uids.add(uid);
+                rules.add(uidRules.valueAt(index));
+                if (Process.isApplicationUid(uid)) {
+                    uids.add(Process.toSdkSandboxUid(uid));
+                    rules.add(uidRules.valueAt(index));
+                }
             }
-            mNetworkManager.setFirewallUidRules(chain, uids, rules);
-            mLogger.firewallRulesChanged(chain, uids, rules);
+            final int[] uidArray = uids.toArray();
+            final int[] ruleArray = rules.toArray();
+            mNetworkManager.setFirewallUidRules(chain, uidArray, ruleArray);
+            mLogger.firewallRulesChanged(chain, uidArray, ruleArray);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem setting firewall uid rules", e);
         } catch (RemoteException e) {
@@ -6241,6 +6254,9 @@
      */
     @GuardedBy("mUidRulesFirstLock")
     private void setUidFirewallRuleUL(int chain, int uid, int rule) {
+        if (mNeverApplyRulesToCoreUids && !isUidValidForRulesUL(uid)) {
+            return;
+        }
         if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
                     "setUidFirewallRuleUL: " + chain + "/" + uid + "/" + rule);
@@ -6249,8 +6265,6 @@
             if (chain == FIREWALL_CHAIN_STANDBY) {
                 mUidFirewallStandbyRules.put(uid, rule);
             }
-            // Note that we do not need keep a separate cache of uid rules for chains that we do
-            // not call #setUidFirewallRulesUL for.
 
             try {
                 mNetworkManager.setFirewallUidRule(chain, uid, rule);
@@ -6295,6 +6309,8 @@
      * Resets all firewall rules associated with an UID.
      */
     private void resetUidFirewallRules(int uid) {
+        // Resetting rules for uids with isUidValidForRulesUL = false should be OK as no rules
+        // should be previously set and the downstream code will skip no-op changes.
         try {
             mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_DOZABLE, uid,
                     FIREWALL_RULE_DEFAULT);
diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig
index 586baf0..7f04e66 100644
--- a/services/core/java/com/android/server/net/flags.aconfig
+++ b/services/core/java/com/android/server/net/flags.aconfig
@@ -27,3 +27,13 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "never_apply_rules_to_core_uids"
+    namespace: "backstage_power"
+    description: "Removes all rule bookkeeping and evaluation logic for core uids and uids without the internet permission"
+    bug: "356956588"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 06e665e..fe5b142 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -761,7 +761,7 @@
             if (isMonitorForRemote()) {
                 mObserver.sendResult(null /* result */);
             }
-            if (isMonitorAnimationOrTransition()) {
+            if (isMonitorAnimationOrTransition() && canCancelAnimations()) {
                 clearBackAnimations(true /* cancel */);
             }
             cancelPendingAnimation();
@@ -2046,11 +2046,22 @@
         }
     }
 
+    /** If the open transition is playing, wait for transition to clear the animation */
+    private boolean canCancelAnimations() {
+        if (!Flags.migratePredictiveBackTransition()) {
+            return true;
+        }
+        return mAnimationHandler.mOpenAnimAdaptor == null
+                || mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition == null;
+    }
+
     void startAnimation() {
         if (!mBackAnimationInProgress) {
             // gesture is already finished, do not start animation
             if (mPendingAnimation != null) {
-                clearBackAnimations(true /* cancel */);
+                if (canCancelAnimations()) {
+                    clearBackAnimations(true /* cancel */);
+                }
                 mPendingAnimation = null;
             }
             return;
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 7a95c2d..2f0ee17 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -129,21 +129,33 @@
     @WindowConfiguration.WindowingMode
     private int getWindowingModeLocked(@NonNull SettingsProvider.SettingsEntry settings,
             @NonNull DisplayContent dc) {
-        int windowingMode = settings.mWindowingMode;
+        final int windowingModeFromDisplaySettings = settings.mWindowingMode;
         // This display used to be in freeform, but we don't support freeform anymore, so fall
         // back to fullscreen.
-        if (windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM
+        if (windowingModeFromDisplaySettings == WindowConfiguration.WINDOWING_MODE_FREEFORM
                 && !mService.mAtmService.mSupportsFreeformWindowManagement) {
             return WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
         }
-        // No record is present so use default windowing mode policy.
-        if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
-            windowingMode = mService.mAtmService.mSupportsFreeformWindowManagement
-                    && (mService.mIsPc || dc.forceDesktopMode())
-                    ? WindowConfiguration.WINDOWING_MODE_FREEFORM
-                    : WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+        if (windowingModeFromDisplaySettings != WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
+            return windowingModeFromDisplaySettings;
         }
-        return windowingMode;
+        // No record is present so use default windowing mode policy.
+        final boolean forceFreeForm = mService.mAtmService.mSupportsFreeformWindowManagement
+                && (mService.mIsPc || dc.forceDesktopMode());
+        if (forceFreeForm) {
+            return WindowConfiguration.WINDOWING_MODE_FREEFORM;
+        }
+        final int currentWindowingMode = dc.getDefaultTaskDisplayArea().getWindowingMode();
+        if (currentWindowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
+            // No record preset in settings + no mode set via the display area policy.
+            // Move to fullscreen as a fallback.
+            return WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+        }
+        if (currentWindowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM) {
+            // Freeform was enabled before but disabled now, the TDA should now move to fullscreen.
+            return WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+        }
+        return currentWindowingMode;
     }
 
     @WindowConfiguration.WindowingMode
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index aee7242..a27ad9a 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -913,52 +913,100 @@
         final var controller = ControllerImpl.createFrom(null /* currentInstance */, List.of(),
                 List.of());
 
-        assertNoAction(controller, false /* forHardware */, items);
-        assertNoAction(controller, true /* forHardware */, hardwareItems);
+        assertNextItemNoAction(controller, false /* forHardware */, items,
+                null /* expectedNext */);
+        assertNextItemNoAction(controller, true /* forHardware */, hardwareItems,
+                null /* expectedNext */);
     }
 
-    /** Verifies that a controller with a single item can't take any actions. */
+    /**
+     * Verifies that a controller with a single item can't update the recency, and cannot switch
+     * away from the item, but allows switching from unknown items to the single item.
+     */
     @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
     @Test
     public void testSingleItemList() {
         final var items = new ArrayList<ImeSubtypeListItem>();
         addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
-                List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+                null, true /* supportsSwitchingToNextInputMethod */);
+        final var unknownItems = new ArrayList<ImeSubtypeListItem>();
+        addTestImeSubtypeListItems(unknownItems, "UnknownIme", "UnknownIme",
+                List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
 
         final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
         addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme",
-                List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+                null, true /* supportsSwitchingToNextInputMethod */);
+        final var unknownHardwareItems = new ArrayList<ImeSubtypeListItem>();
+        addTestImeSubtypeListItems(unknownHardwareItems, "HardwareUnknownIme", "HardwareUnknownIme",
+                List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
 
         final var controller = ControllerImpl.createFrom(null /* currentInstance */,
-                List.of(items.get(0)), List.of(hardwareItems.get(0)));
+                items, hardwareItems);
 
-        assertNoAction(controller, false /* forHardware */, items);
-        assertNoAction(controller, true /* forHardware */, hardwareItems);
+        assertNextItemNoAction(controller, false /* forHardware */, items,
+                null /* expectedNext */);
+        assertNextItemNoAction(controller, false /* forHardware */, unknownItems,
+                items.get(0));
+        assertNextItemNoAction(controller, true /* forHardware */, hardwareItems,
+                null /* expectedNext */);
+        assertNextItemNoAction(controller, true /* forHardware */, unknownHardwareItems,
+                hardwareItems.get(0));
     }
 
-    /** Verifies that a controller can't take any actions for unknown items. */
+    /**
+     * Verifies that the recency cannot be updated for unknown items, but switching from unknown
+     * items reaches the most recent known item.
+     */
     @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
     @Test
     public void testUnknownItems() {
         final var items = new ArrayList<ImeSubtypeListItem>();
         addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
-                List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+                List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+
+        final var english = items.get(0);
+        final var french = items.get(1);
+        final var italian = items.get(2);
+
         final var unknownItems = new ArrayList<ImeSubtypeListItem>();
         addTestImeSubtypeListItems(unknownItems, "UnknownIme", "UnknownIme",
-                List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+                List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
 
         final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
         addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme",
-                List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+                List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
         final var unknownHardwareItems = new ArrayList<ImeSubtypeListItem>();
         addTestImeSubtypeListItems(unknownHardwareItems, "HardwareUnknownIme", "HardwareUnknownIme",
-                List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+                List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
 
         final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
                 hardwareItems);
 
-        assertNoAction(controller, false /* forHardware */, unknownItems);
-        assertNoAction(controller, true /* forHardware */, unknownHardwareItems);
+        assertTrue("Recency updated for french IME", onUserAction(controller, french));
+
+        final var recencyItems = List.of(french, english, italian);
+
+        assertNextItemNoAction(controller, false /* forHardware */, unknownItems,
+                french);
+        assertNextItemNoAction(controller, true /* forHardware */, unknownHardwareItems,
+                hardwareItems.get(0));
+
+        // Known items must not be able to switch to unknown items.
+        assertNextOrder(controller, false /* forHardware */, MODE_STATIC, items,
+                List.of(items));
+        assertNextOrder(controller, false /* forHardware */, MODE_RECENT, recencyItems,
+                List.of(recencyItems));
+        assertNextOrder(controller, false /* forHardware */, MODE_AUTO, true /* forward */,
+                recencyItems, List.of(recencyItems));
+        assertNextOrder(controller, false /* forHardware */, MODE_AUTO, false /* forward */,
+                items.reversed(), List.of(items.reversed()));
+
+        assertNextOrder(controller, true /* forHardware */, MODE_STATIC, hardwareItems,
+                List.of(hardwareItems));
+        assertNextOrder(controller, true /* forHardware */, MODE_RECENT, hardwareItems,
+                List.of(hardwareItems));
+        assertNextOrder(controller, true /* forHardware */, MODE_AUTO, hardwareItems,
+                List.of(hardwareItems));
     }
 
     /** Verifies that the IME name does influence the comparison order. */
@@ -1199,25 +1247,26 @@
     }
 
     /**
-     * Verifies that no next items can be found, and the recency cannot be updated for the
+     * Verifies that the expected next item is returned, and the recency cannot be updated for the
      * given items.
      *
-     * @param controller  the controller to verify the items on.
-     * @param forHardware whether to try finding the next hardware item, or software item.
-     * @param items       the list of items to verify.
+     * @param controller   the controller to verify the items on.
+     * @param forHardware  whether to try finding the next hardware item, or software item.
+     * @param items        the list of items to verify.
+     * @param expectedNext the expected next item.
      */
-    private void assertNoAction(@NonNull ControllerImpl controller, boolean forHardware,
-            @NonNull List<ImeSubtypeListItem> items) {
+    private void assertNextItemNoAction(@NonNull ControllerImpl controller, boolean forHardware,
+            @NonNull List<ImeSubtypeListItem> items, @Nullable ImeSubtypeListItem expectedNext) {
         for (var item : items) {
             for (int mode = MODE_STATIC; mode <= MODE_AUTO; mode++) {
                 assertNextItem(controller, forHardware, false /* onlyCurrentIme */, mode,
-                        false /* forward */, item, null /* expectedNext */);
+                        false /* forward */, item, expectedNext);
                 assertNextItem(controller, forHardware, false /* onlyCurrentIme */, mode,
-                        true /* forward */, item, null /* expectedNext */);
+                        true /* forward */, item, expectedNext);
                 assertNextItem(controller, forHardware, true /* onlyCurrentIme */, mode,
-                        false /* forward */, item, null /* expectedNext */);
+                        false /* forward */, item, expectedNext);
                 assertNextItem(controller, forHardware, true /* onlyCurrentIme */, mode,
-                        true /* forward */, item, null /* expectedNext */);
+                        true /* forward */, item, expectedNext);
             }
 
             assertFalse("User action shouldn't have updated the recency.",
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 3d68849..dddab65 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -108,6 +108,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
@@ -165,6 +166,7 @@
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.SimpleClock;
 import android.os.SystemClock;
@@ -197,6 +199,7 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent;
 import com.android.internal.util.test.FsUtil;
@@ -2310,6 +2313,70 @@
         assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
     }
 
+    @SuppressWarnings("GuardedBy") // For not holding mUidRulesFirstLock
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NEVER_APPLY_RULES_TO_CORE_UIDS)
+    public void testRulesNeverAppliedToCoreUids() throws Exception {
+        clearInvocations(mNetworkManager);
+
+        final int coreAppId = Process.FIRST_APPLICATION_UID - 102;
+        final int coreUid = UserHandle.getUid(USER_ID, coreAppId);
+
+        // Enable all restrictions and add this core uid to all allowlists.
+        mService.mDeviceIdleMode = true;
+        mService.mRestrictPower = true;
+        setRestrictBackground(true);
+        expectHasUseRestrictedNetworksPermission(coreUid, true);
+        enableRestrictedMode(true);
+        final NetworkPolicyManagerInternal internal = LocalServices.getService(
+                NetworkPolicyManagerInternal.class);
+        internal.setLowPowerStandbyActive(true);
+        internal.setLowPowerStandbyAllowlist(new int[]{coreUid});
+        internal.onTempPowerSaveWhitelistChange(coreAppId, true, REASON_OTHER, "testing");
+
+        when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean()))
+                .thenReturn(new int[]{coreAppId});
+        mPowerAllowlistReceiver.onReceive(mServiceContext, null);
+
+        // A normal uid would undergo a rule change from denied to allowed on all chains, but we
+        // should not request any rule change for this core uid.
+        verify(mNetworkManager, never()).setFirewallUidRule(anyInt(), eq(coreUid), anyInt());
+        verify(mNetworkManager, never()).setFirewallUidRules(anyInt(),
+                argThat(ar -> ArrayUtils.contains(ar, coreUid)), any(int[].class));
+    }
+
+    @SuppressWarnings("GuardedBy") // For not holding mUidRulesFirstLock
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NEVER_APPLY_RULES_TO_CORE_UIDS)
+    public void testRulesNeverAppliedToUidsWithoutInternetPermission() throws Exception {
+        clearInvocations(mNetworkManager);
+
+        mService.mInternetPermissionMap.clear();
+        expectHasInternetPermission(UID_A, false);
+
+        // Enable all restrictions and add this uid to all allowlists.
+        mService.mDeviceIdleMode = true;
+        mService.mRestrictPower = true;
+        setRestrictBackground(true);
+        expectHasUseRestrictedNetworksPermission(UID_A, true);
+        enableRestrictedMode(true);
+        final NetworkPolicyManagerInternal internal = LocalServices.getService(
+                NetworkPolicyManagerInternal.class);
+        internal.setLowPowerStandbyActive(true);
+        internal.setLowPowerStandbyAllowlist(new int[]{UID_A});
+        internal.onTempPowerSaveWhitelistChange(APP_ID_A, true, REASON_OTHER, "testing");
+
+        when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean()))
+                .thenReturn(new int[]{APP_ID_A});
+        mPowerAllowlistReceiver.onReceive(mServiceContext, null);
+
+        // A normal uid would undergo a rule change from denied to allowed on all chains, but we
+        // should not request any rule this uid without the INTERNET permission.
+        verify(mNetworkManager, never()).setFirewallUidRule(anyInt(), eq(UID_A), anyInt());
+        verify(mNetworkManager, never()).setFirewallUidRules(anyInt(),
+                argThat(ar -> ArrayUtils.contains(ar, UID_A)), any(int[].class));
+    }
+
     private boolean isUidState(int uid, int procState, int procStateSeq, int capability) {
         final NetworkPolicyManager.UidState uidState = mService.getUidStateForTest(uid);
         if (uidState == null) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index e2524a2..ddadbc4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -115,6 +115,17 @@
     }
 
     @Test
+    public void testPrimaryDisplayUnchanged_whenWindowingModeAlreadySet_NoFreeformSupport() {
+        mPrimaryDisplay.getDefaultTaskDisplayArea().setWindowingMode(
+                WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
+
+        mDisplayWindowSettings.applySettingsToDisplayLocked(mPrimaryDisplay);
+
+        assertEquals(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW,
+                mPrimaryDisplay.getDefaultTaskDisplayArea().getWindowingMode());
+    }
+
+    @Test
     public void testPrimaryDisplayDefaultToFullscreen_HasFreeformSupport_NonPc_NoDesktopMode() {
         mWm.mAtmService.mSupportsFreeformWindowManagement = true;
 
diff --git a/tests/Internal/Android.bp b/tests/Internal/Android.bp
index 827ff4f..ad98e47 100644
--- a/tests/Internal/Android.bp
+++ b/tests/Internal/Android.bp
@@ -24,6 +24,7 @@
         "flickerlib-parsers",
         "perfetto_trace_java_protos",
         "flickerlib-trace_processor_shell",
+        "ravenwood-junit",
     ],
     java_resource_dirs: ["res"],
     certificate: "platform",
@@ -39,6 +40,7 @@
         "platform-test-annotations",
     ],
     srcs: [
+        "src/com/android/internal/graphics/ColorUtilsTest.java",
         "src/com/android/internal/util/ParcellingTests.java",
     ],
     auto_gen_config: true,
diff --git a/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java b/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java
index d0bb8e3..38a22f2 100644
--- a/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java
+++ b/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java
@@ -19,14 +19,19 @@
 import static org.junit.Assert.assertTrue;
 
 import android.graphics.Color;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.Rule;
 import org.junit.Test;
 
 @SmallTest
 public class ColorUtilsTest {
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     @Test
     public void calculateMinimumBackgroundAlpha_satisfiestContrast() {
 
diff --git a/tests/Internal/src/com/android/internal/util/ParcellingTests.java b/tests/Internal/src/com/android/internal/util/ParcellingTests.java
index 65a3436..fb63422 100644
--- a/tests/Internal/src/com/android/internal/util/ParcellingTests.java
+++ b/tests/Internal/src/com/android/internal/util/ParcellingTests.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -26,6 +27,7 @@
 
 import com.android.internal.util.Parcelling.BuiltIn.ForInstant;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -38,6 +40,9 @@
 @RunWith(JUnit4.class)
 public class ParcellingTests {
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private Parcel mParcel = Parcel.obtain();
 
     @Test
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 3f9016b..f43cf52 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -113,6 +113,7 @@
         "io/ZipArchive.cpp",
         "link/AutoVersioner.cpp",
         "link/FeatureFlagsFilter.cpp",
+        "link/FlagDisabledResourceRemover.cpp",
         "link/ManifestFixer.cpp",
         "link/NoDefaultResourceRemover.cpp",
         "link/PrivateAttributeMover.cpp",
@@ -189,6 +190,8 @@
         "integration-tests/CommandTests/**/*",
         "integration-tests/ConvertTest/**/*",
         "integration-tests/DumpTest/**/*",
+        ":resource-flagging-test-app-apk",
+        ":resource-flagging-test-app-r-java",
     ],
 }
 
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 9444dd9..1c85e9f 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -690,9 +690,7 @@
         resource_format = item_iter->second.format;
       }
 
-      // Don't bother parsing the item if it is behind a disabled flag
-      if (out_resource->flag_status != FlagStatus::Disabled &&
-          !ParseItem(parser, out_resource, resource_format)) {
+      if (!ParseItem(parser, out_resource, resource_format)) {
         return false;
       }
       return true;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 2e6ad13..b59b165 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -69,13 +69,8 @@
     return TestParse(str, ConfigDescription{});
   }
 
-  ::testing::AssertionResult TestParse(StringPiece str, ResourceParserOptions parserOptions) {
-    return TestParse(str, ConfigDescription{}, parserOptions);
-  }
-
-  ::testing::AssertionResult TestParse(
-      StringPiece str, const ConfigDescription& config,
-      ResourceParserOptions parserOptions = ResourceParserOptions()) {
+  ::testing::AssertionResult TestParse(StringPiece str, const ConfigDescription& config) {
+    ResourceParserOptions parserOptions;
     ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, config,
                           parserOptions);
 
@@ -247,19 +242,6 @@
   EXPECT_FALSE(TestParse(R"(<string name="foo4" translatable="yes">Translate</string>)"));
 }
 
-TEST_F(ResourceParserTest, ParseStringBehindDisabledFlag) {
-  FeatureFlagProperties flag_properties(true, false);
-  ResourceParserOptions options;
-  options.feature_flag_values = {{"falseFlag", flag_properties}};
-  ASSERT_TRUE(TestParse(
-      R"(<string name="foo" android:featureFlag="falseFlag"
-              xmlns:android="http://schemas.android.com/apk/res/android">foo</string>)",
-      options));
-
-  String* str = test::GetValue<String>(&table_, "string/foo");
-  ASSERT_THAT(str, IsNull());
-}
-
 TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) {
   std::string input = R"(
       <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 642a561..56f5288 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -57,6 +57,7 @@
 #include "java/ManifestClassGenerator.h"
 #include "java/ProguardRules.h"
 #include "link/FeatureFlagsFilter.h"
+#include "link/FlagDisabledResourceRemover.h"
 #include "link/Linkers.h"
 #include "link/ManifestFixer.h"
 #include "link/NoDefaultResourceRemover.h"
@@ -1840,11 +1841,57 @@
     return validate(attr->value);
   }
 
+  class FlagDisabledStringVisitor : public DescendingValueVisitor {
+   public:
+    using DescendingValueVisitor::Visit;
+
+    explicit FlagDisabledStringVisitor(android::StringPool& string_pool)
+        : string_pool_(string_pool) {
+    }
+
+    void Visit(RawString* value) override {
+      value->value = string_pool_.MakeRef("");
+    }
+
+    void Visit(String* value) override {
+      value->value = string_pool_.MakeRef("");
+    }
+
+    void Visit(StyledString* value) override {
+      value->value = string_pool_.MakeRef(android::StyleString{{""}, {}});
+    }
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(FlagDisabledStringVisitor);
+    android::StringPool& string_pool_;
+  };
+
   // Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
   // to the IArchiveWriter.
   bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest,
                 ResourceTable* table) {
     TRACE_CALL();
+
+    FlagDisabledStringVisitor visitor(table->string_pool);
+
+    for (auto& package : table->packages) {
+      for (auto& type : package->types) {
+        for (auto& entry : type->entries) {
+          for (auto& config_value : entry->values) {
+            if (config_value->flag_status == FlagStatus::Disabled) {
+              config_value->value->Accept(&visitor);
+            }
+          }
+        }
+      }
+    }
+
+    if (!FlagDisabledResourceRemover{}.Consume(context_, table)) {
+      context_->GetDiagnostics()->Error(android::DiagMessage()
+                                        << "failed removing resources behind disabled flags");
+      return 1;
+    }
+
     const bool keep_raw_values = (context_->GetPackageType() == PackageType::kStaticLib)
                                  || options_.keep_raw_values;
     bool result = FlattenXml(context_, *manifest, kAndroidManifestPath, keep_raw_values,
@@ -2331,6 +2378,12 @@
       return 1;
     };
 
+    if (options_.generate_java_class_path || options_.generate_text_symbols_path) {
+      if (!GenerateJavaClasses()) {
+        return 1;
+      }
+    }
+
     if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), &final_table_)) {
       return 1;
     }
@@ -2339,12 +2392,6 @@
       return 1;
     }
 
-    if (options_.generate_java_class_path || options_.generate_text_symbols_path) {
-      if (!GenerateJavaClasses()) {
-        return 1;
-      }
-    }
-
     if (!WriteProguardFile(options_.generate_proguard_rules_path, proguard_keep_set)) {
       return 1;
     }
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
new file mode 100644
index 0000000..5932271
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
@@ -0,0 +1,81 @@
+// Copyright (C) 2024 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
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+    default_team: "trendy_team_android_resources",
+}
+
+genrule {
+    name: "resource-flagging-test-app-compile",
+    tools: ["aapt2"],
+    srcs: [
+        "res/values/bools.xml",
+        "res/values/bools2.xml",
+        "res/values/strings.xml",
+    ],
+    out: [
+        "values_bools.arsc.flat",
+        "values_bools2.arsc.flat",
+        "values_strings.arsc.flat",
+    ],
+    cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
+        "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
+}
+
+genrule {
+    name: "resource-flagging-test-app-apk",
+    tools: ["aapt2"],
+    // The first input file in the list must be the manifest
+    srcs: [
+        "AndroidManifest.xml",
+        ":resource-flagging-test-app-compile",
+    ],
+    out: [
+        "resapp.apk",
+    ],
+    cmd: "$(location aapt2) link -o $(out) --manifest $(in)",
+}
+
+genrule {
+    name: "resource-flagging-test-app-r-java",
+    tools: ["aapt2"],
+    // The first input file in the list must be the manifest
+    srcs: [
+        "AndroidManifest.xml",
+        ":resource-flagging-test-app-compile",
+    ],
+    out: [
+        "resource-flagging-java/com/android/intenal/flaggedresources/R.java",
+    ],
+    cmd: "$(location aapt2) link -o $(genDir)/resapp.apk --java $(genDir)/resource-flagging-java --manifest $(in)",
+}
+
+java_genrule {
+    name: "resource-flagging-test-app-apk-as-resource",
+    srcs: [
+        ":resource-flagging-test-app-apk",
+    ],
+    out: ["apks_as_resources.res.zip"],
+    tools: ["soong_zip"],
+
+    cmd: "mkdir -p $(genDir)/res/raw && " +
+        "cp $(in) $(genDir)/res/raw/$$(basename $(in)) && " +
+        "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res",
+}
diff --git a/core/tests/resourceflaggingtests/TestAppAndroidManifest.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/AndroidManifest.xml
similarity index 100%
rename from core/tests/resourceflaggingtests/TestAppAndroidManifest.xml
rename to tools/aapt2/integration-tests/FlaggedResourcesTest/AndroidManifest.xml
diff --git a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
similarity index 82%
rename from core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml
rename to tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
index 8d01465..3e094fb 100644
--- a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
@@ -7,4 +7,6 @@
     <bool name="res2" android:featureFlag="test.package.trueFlag">true</bool>
 
     <bool name="res3">false</bool>
+
+    <bool name="res4" android:featureFlag="test.package.falseFlag">true</bool>
 </resources>
\ No newline at end of file
diff --git a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools2.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml
similarity index 100%
rename from core/tests/resourceflaggingtests/flagged_resources_res/values/bools2.xml
rename to tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml
new file mode 100644
index 0000000..5c0fca1
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <string name="str">plain string</string>
+
+    <string name="str1" android:featureFlag="test.package.falseFlag">DONTFIND</string>
+</resources>
\ No newline at end of file
diff --git a/tools/aapt2/link/FlagDisabledResourceRemover.cpp b/tools/aapt2/link/FlagDisabledResourceRemover.cpp
new file mode 100644
index 0000000..e3289e2
--- /dev/null
+++ b/tools/aapt2/link/FlagDisabledResourceRemover.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include "link/FlagDisabledResourceRemover.h"
+
+#include <algorithm>
+
+#include "ResourceTable.h"
+
+using android::ConfigDescription;
+
+namespace aapt {
+
+static bool KeepResourceEntry(const std::unique_ptr<ResourceEntry>& entry) {
+  if (entry->values.empty()) {
+    return true;
+  }
+  const auto end_iter = entry->values.end();
+  const auto remove_iter =
+      std::stable_partition(entry->values.begin(), end_iter,
+                            [](const std::unique_ptr<ResourceConfigValue>& value) -> bool {
+                              return value->flag_status != FlagStatus::Disabled;
+                            });
+
+  bool keep = remove_iter != entry->values.begin();
+
+  entry->values.erase(remove_iter, end_iter);
+  return keep;
+}
+
+bool FlagDisabledResourceRemover::Consume(IAaptContext* context, ResourceTable* table) {
+  for (auto& pkg : table->packages) {
+    for (auto& type : pkg->types) {
+      const auto end_iter = type->entries.end();
+      const auto remove_iter = std::stable_partition(
+          type->entries.begin(), end_iter, [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
+            return KeepResourceEntry(entry);
+          });
+
+      type->entries.erase(remove_iter, end_iter);
+    }
+  }
+  return true;
+}
+
+}  // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/link/FlagDisabledResourceRemover.h b/tools/aapt2/link/FlagDisabledResourceRemover.h
new file mode 100644
index 0000000..2db2cb4
--- /dev/null
+++ b/tools/aapt2/link/FlagDisabledResourceRemover.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#pragma once
+
+#include "android-base/macros.h"
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+// Removes any resource that are behind disabled flags.
+class FlagDisabledResourceRemover : public IResourceTableConsumer {
+ public:
+  FlagDisabledResourceRemover() = default;
+
+  bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FlagDisabledResourceRemover);
+};
+
+}  // namespace aapt
diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp
new file mode 100644
index 0000000..c901b58
--- /dev/null
+++ b/tools/aapt2/link/FlaggedResources_test.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "LoadedApk.h"
+#include "cmd/Dump.h"
+#include "io/StringStream.h"
+#include "test/Test.h"
+#include "text/Printer.h"
+
+using ::aapt::io::StringOutputStream;
+using ::aapt::text::Printer;
+using testing::Eq;
+using testing::Ne;
+
+namespace aapt {
+
+using FlaggedResourcesTest = CommandTestFixture;
+
+static android::NoOpDiagnostics noop_diag;
+
+void DumpStringPoolToString(LoadedApk* loaded_apk, std::string* output) {
+  StringOutputStream output_stream(output);
+  Printer printer(&output_stream);
+
+  DumpStringsCommand command(&printer, &noop_diag);
+  ASSERT_EQ(command.Dump(loaded_apk), 0);
+  output_stream.Flush();
+}
+
+void DumpResourceTableToString(LoadedApk* loaded_apk, std::string* output) {
+  StringOutputStream output_stream(output);
+  Printer printer(&output_stream);
+
+  DumpTableCommand command(&printer, &noop_diag);
+  ASSERT_EQ(command.Dump(loaded_apk), 0);
+  output_stream.Flush();
+}
+
+void DumpChunksToString(LoadedApk* loaded_apk, std::string* output) {
+  StringOutputStream output_stream(output);
+  Printer printer(&output_stream);
+
+  DumpChunks command(&printer, &noop_diag);
+  ASSERT_EQ(command.Dump(loaded_apk), 0);
+  output_stream.Flush();
+}
+
+TEST_F(FlaggedResourcesTest, DisabledStringRemovedFromPool) {
+  auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"});
+  auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
+
+  std::string output;
+  DumpStringPoolToString(loaded_apk.get(), &output);
+
+  std::string excluded = "DONTFIND";
+  ASSERT_EQ(output.find(excluded), std::string::npos);
+}
+
+TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTable) {
+  auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"});
+  auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
+
+  std::string output;
+  DumpResourceTableToString(loaded_apk.get(), &output);
+}
+
+TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTableChunks) {
+  auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"});
+  auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
+
+  std::string output;
+  DumpChunksToString(loaded_apk.get(), &output);
+
+  ASSERT_EQ(output.find("res4"), std::string::npos);
+  ASSERT_EQ(output.find("str1"), std::string::npos);
+}
+
+TEST_F(FlaggedResourcesTest, DisabledResourcesInRJava) {
+  auto r_path = file::BuildPath({android::base::GetExecutableDirectory(), "resource-flagging-java",
+                                 "com", "android", "intenal", "flaggedresources", "R.java"});
+  std::string r_contents;
+  ::android::base::ReadFileToString(r_path, &r_contents);
+
+  ASSERT_NE(r_contents.find("public static final int res4"), std::string::npos);
+  ASSERT_NE(r_contents.find("public static final int str1"), std::string::npos);
+}
+
+}  // namespace aapt
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 5dde265..36bfbef 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -58,17 +58,19 @@
 
         // Dump the classes, if specified.
         options.inputJarDumpFile.ifSet {
-            PrintWriter(it).use { pw -> allClasses.dump(pw) }
-            log.i("Dump file created at $it")
+            log.iTime("Dump file created at $it") {
+                PrintWriter(it).use { pw -> allClasses.dump(pw) }
+            }
         }
 
         options.inputJarAsKeepAllFile.ifSet {
-            PrintWriter(it).use {
-                pw -> allClasses.forEach {
-                    classNode -> printAsTextPolicy(pw, classNode)
+            log.iTime("Dump file created at $it") {
+                PrintWriter(it).use { pw ->
+                    allClasses.forEach { classNode ->
+                        printAsTextPolicy(pw, classNode)
+                    }
                 }
             }
-            log.i("Dump file created at $it")
         }
 
         // Build the filters.
@@ -91,16 +93,18 @@
 
         // Dump statistics, if specified.
         options.statsFile.ifSet {
-            PrintWriter(it).use { pw -> stats.dumpOverview(pw) }
-            log.i("Dump file created at $it")
+            log.iTime("Dump file created at $it") {
+                PrintWriter(it).use { pw -> stats.dumpOverview(pw) }
+            }
         }
         options.apiListFile.ifSet {
-            PrintWriter(it).use { pw ->
-                // TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed
-                // framework-minus-apex.jar so that we can dump inherited methods from it.
-                ApiDumper(pw, allClasses, null, filter).dump()
+            log.iTime("API list file created at $it") {
+                PrintWriter(it).use { pw ->
+                    // TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed
+                    // framework-minus-apex.jar so that we can dump inherited methods from it.
+                    ApiDumper(pw, allClasses, null, filter).dump()
+                }
             }
-            log.i("API list file created at $it")
         }
     }
 
@@ -221,47 +225,48 @@
         log.i("Converting %s into [stub: %s, impl: %s] ...", inJar, outStubJar, outImplJar)
         log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled")
 
-        val start = System.currentTimeMillis()
+        log.iTime("Transforming jar") {
+            val packageRedirector = PackageRedirectRemapper(options.packageRedirects)
 
-        val packageRedirector = PackageRedirectRemapper(options.packageRedirects)
+            var itemIndex = 0
+            var numItemsProcessed = 0
+            var numItems = -1 // == Unknown
 
-        var itemIndex = 0
-        var numItemsProcessed = 0
-        var numItems = -1 // == Unknown
+            log.withIndent {
+                // Open the input jar file and process each entry.
+                ZipFile(inJar).use { inZip ->
 
-        log.withIndent {
-            // Open the input jar file and process each entry.
-            ZipFile(inJar).use { inZip ->
+                    numItems = inZip.size()
+                    val shardStart = numItems * shard / numShards
+                    val shardNextStart = numItems * (shard + 1) / numShards
 
-                numItems = inZip.size()
-                val shardStart = numItems * shard / numShards
-                val shardNextStart = numItems * (shard + 1) / numShards
-
-                maybeWithZipOutputStream(outStubJar) { stubOutStream ->
-                    maybeWithZipOutputStream(outImplJar) { implOutStream ->
-                        val inEntries = inZip.entries()
-                        while (inEntries.hasMoreElements()) {
-                            val entry = inEntries.nextElement()
-                            val inShard = (shardStart <= itemIndex) && (itemIndex < shardNextStart)
-                            itemIndex++
-                            if (!inShard) {
-                                continue
-                            }
-                            convertSingleEntry(inZip, entry, stubOutStream, implOutStream,
+                    maybeWithZipOutputStream(outStubJar) { stubOutStream ->
+                        maybeWithZipOutputStream(outImplJar) { implOutStream ->
+                            val inEntries = inZip.entries()
+                            while (inEntries.hasMoreElements()) {
+                                val entry = inEntries.nextElement()
+                                val inShard = (shardStart <= itemIndex)
+                                        && (itemIndex < shardNextStart)
+                                itemIndex++
+                                if (!inShard) {
+                                    continue
+                                }
+                                convertSingleEntry(
+                                    inZip, entry, stubOutStream, implOutStream,
                                     filter, packageRedirector, remapper,
-                                    enableChecker, classes, errors, stats)
-                            numItemsProcessed++
+                                    enableChecker, classes, errors, stats
+                                )
+                                numItemsProcessed++
+                            }
+                            log.i("Converted all entries.")
                         }
-                        log.i("Converted all entries.")
                     }
+                    outStubJar?.let { log.i("Created stub: $it") }
+                    outImplJar?.let { log.i("Created impl: $it") }
                 }
-                outStubJar?.let { log.i("Created stub: $it") }
-                outImplJar?.let { log.i("Created impl: $it") }
             }
+            log.i("%d / %d item(s) processed.", numItemsProcessed, numItems)
         }
-        val end = System.currentTimeMillis()
-        log.i("Done transforming the jar in %.1f second(s). %d / %d item(s) processed.",
-            (end - start) / 1000.0, numItemsProcessed, numItems)
     }
 
     private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipOutputStream?) -> T): T {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
index 18065ba..ee4a06f 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
@@ -185,6 +185,16 @@
         println(LogLevel.Debug, format, *args)
     }
 
+    inline fun <T> iTime(message: String, block: () -> T): T {
+        val start = System.currentTimeMillis()
+        val ret = block()
+        val end = System.currentTimeMillis()
+
+        log.i("%s: took %.1f second(s).", message, (end - start) / 1000.0)
+
+        return ret
+    }
+
     inline fun forVerbose(block: () -> Unit) {
         if (isEnabled(LogLevel.Verbose)) {
             block()
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
index 92906a7..2607df6 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
@@ -184,49 +184,50 @@
          * Load all the classes, without code.
          */
         fun loadClassStructures(inJar: String): ClassNodes {
-            log.i("Reading class structure from $inJar ...")
-            val start = System.currentTimeMillis()
+            log.iTime("Reading class structure from $inJar") {
+                val allClasses = ClassNodes()
 
-            val allClasses = ClassNodes()
+                log.withIndent {
+                    ZipFile(inJar).use { inZip ->
+                        val inEntries = inZip.entries()
 
-            log.withIndent {
-                ZipFile(inJar).use { inZip ->
-                    val inEntries = inZip.entries()
+                        while (inEntries.hasMoreElements()) {
+                            val entry = inEntries.nextElement()
 
-                    while (inEntries.hasMoreElements()) {
-                        val entry = inEntries.nextElement()
-
-                        BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
-                            if (entry.name.endsWith(".class")) {
-                                val cr = ClassReader(bis)
-                                val cn = ClassNode()
-                                cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG
-                                        or ClassReader.SKIP_FRAMES)
-                                if (!allClasses.addClass(cn)) {
-                                    log.w("Duplicate class found: ${cn.name}")
-                                }
-                            } else if (entry.name.endsWith(".dex")) {
-                                // Seems like it's an ART jar file. We can't process it.
-                                // It's a fatal error.
-                                throw InvalidJarFileException(
-                                    "$inJar is not a desktop jar file. It contains a *.dex file.")
-                            } else {
-                                // Unknown file type. Skip.
-                                while (bis.available() > 0) {
-                                    bis.skip((1024 * 1024).toLong())
+                            BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+                                if (entry.name.endsWith(".class")) {
+                                    val cr = ClassReader(bis)
+                                    val cn = ClassNode()
+                                    cr.accept(
+                                        cn, ClassReader.SKIP_CODE
+                                                or ClassReader.SKIP_DEBUG
+                                                or ClassReader.SKIP_FRAMES
+                                    )
+                                    if (!allClasses.addClass(cn)) {
+                                        log.w("Duplicate class found: ${cn.name}")
+                                    }
+                                } else if (entry.name.endsWith(".dex")) {
+                                    // Seems like it's an ART jar file. We can't process it.
+                                    // It's a fatal error.
+                                    throw InvalidJarFileException(
+                                        "$inJar is not a desktop jar file."
+                                        + " It contains a *.dex file."
+                                    )
+                                } else {
+                                    // Unknown file type. Skip.
+                                    while (bis.available() > 0) {
+                                        bis.skip((1024 * 1024).toLong())
+                                    }
                                 }
                             }
                         }
                     }
                 }
+                if (allClasses.size == 0) {
+                    log.w("$inJar contains no *.class files.")
+                }
+                return allClasses
             }
-            if (allClasses.size == 0) {
-                log.w("$inJar contains no *.class files.")
-            }
-
-            val end = System.currentTimeMillis()
-            log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0)
-            return allClasses
         }
     }
 }
\ No newline at end of file