diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index a271d063d1f97bf4dabaf41ef55ac94685285f66..09f50a8daae6a02a4ba5107dc0967f56e2c954f3 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -36,6 +36,7 @@ aconfig_srcjars = [
     ":hwui_flags_java_lib{.generated_srcjars}",
     ":display_flags_lib{.generated_srcjars}",
     ":android.multiuser.flags-aconfig-java{.generated_srcjars}",
+    ":android.app.flags-aconfig-java{.generated_srcjars}",
 ]
 
 filegroup {
@@ -327,3 +328,16 @@ java_aconfig_library {
     aconfig_declarations: "android.multiuser.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// Activity Manager
+aconfig_declarations {
+    name: "android.app.flags-aconfig",
+    package: "android.app",
+    srcs: ["core/java/android/app/activity_manager.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.app.flags-aconfig-java",
+    aconfig_declarations: "android.app.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 8989b10675d335f64fca963a5a8f4b14846df9cd..56a69b46b12c7ea5628f3374ce939f5baddc2523 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -67,6 +67,7 @@ droidstubs {
             tag: ".removed-api.txt",
         },
     ],
+    api_surface: "public",
 }
 
 priv_apps = " --show-annotation android.annotation.SystemApi\\(" +
@@ -120,6 +121,7 @@ droidstubs {
             tag: ".removed-api.txt",
         },
     ],
+    api_surface: "system",
 }
 
 droidstubs {
@@ -166,6 +168,7 @@ droidstubs {
             tag: ".removed-api.txt",
         },
     ],
+    api_surface: "test",
 }
 
 droidstubs {
@@ -205,6 +208,7 @@ droidstubs {
             tag: ".removed-api.txt",
         },
     ],
+    api_surface: "module-lib",
 }
 
 /////////////////////////////////////////////////////////////////////
@@ -381,8 +385,8 @@ java_defaults {
 java_api_library {
     name: "android-non-updatable.stubs.from-text",
     api_surface: "public",
-    api_files: [
-        ":non-updatable-current.txt",
+    api_contributions: [
+        "api-stubs-docs-non-updatable.api.contribution",
     ],
     defaults: ["android-non-updatable_from_text_defaults"],
     full_api_surface_stub: "android_stubs_current.from-text",
@@ -391,9 +395,9 @@ java_api_library {
 java_api_library {
     name: "android-non-updatable.stubs.system.from-text",
     api_surface: "system",
-    api_files: [
-        ":non-updatable-current.txt",
-        ":non-updatable-system-current.txt",
+    api_contributions: [
+        "api-stubs-docs-non-updatable.api.contribution",
+        "system-api-stubs-docs-non-updatable.api.contribution",
     ],
     defaults: ["android-non-updatable_from_text_defaults"],
     full_api_surface_stub: "android_system_stubs_current.from-text",
@@ -402,10 +406,10 @@ java_api_library {
 java_api_library {
     name: "android-non-updatable.stubs.test.from-text",
     api_surface: "test",
-    api_files: [
-        ":non-updatable-current.txt",
-        ":non-updatable-system-current.txt",
-        ":non-updatable-test-current.txt",
+    api_contributions: [
+        "api-stubs-docs-non-updatable.api.contribution",
+        "system-api-stubs-docs-non-updatable.api.contribution",
+        "test-api-stubs-docs-non-updatable.api.contribution",
     ],
     defaults: ["android-non-updatable_from_text_defaults"],
     full_api_surface_stub: "android_test_stubs_current.from-text",
@@ -414,10 +418,10 @@ java_api_library {
 java_api_library {
     name: "android-non-updatable.stubs.module_lib.from-text",
     api_surface: "module_lib",
-    api_files: [
-        ":non-updatable-current.txt",
-        ":non-updatable-system-current.txt",
-        ":non-updatable-module-lib-current.txt",
+    api_contributions: [
+        "api-stubs-docs-non-updatable.api.contribution",
+        "system-api-stubs-docs-non-updatable.api.contribution",
+        "module-lib-api-stubs-docs-non-updatable.api.contribution",
     ],
     defaults: ["android-non-updatable_from_text_defaults"],
     full_api_surface_stub: "android_module_lib_stubs_current_full.from-text",
@@ -615,7 +619,6 @@ java_defaults {
     name: "android_test_stubs_current_contributions",
     api_surface: "test",
     api_contributions: [
-        "test-api-stubs-docs-non-updatable.api.contribution",
         "framework-virtualization.stubs.source.test.api.contribution",
         "framework-location.stubs.source.test.api.contribution",
     ],
@@ -690,6 +693,7 @@ java_api_library {
     api_contributions: [
         "api-stubs-docs-non-updatable.api.contribution",
         "system-api-stubs-docs-non-updatable.api.contribution",
+        "test-api-stubs-docs-non-updatable.api.contribution",
     ],
     visibility: ["//visibility:public"],
 }
diff --git a/core/api/current.txt b/core/api/current.txt
index 05ec9ba71a4c5834ff173a90e3d8152d07ed47d3..d8e3abfcc33d995b6c251bec27f6ce7483ef175a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4616,9 +4616,9 @@ package android.app {
 
   public class ActivityManager {
     method public int addAppTask(@NonNull android.app.Activity, @NonNull android.content.Intent, @Nullable android.app.ActivityManager.TaskDescription, @NonNull android.graphics.Bitmap);
-    method public void addStartInfoTimestamp(@IntRange(from=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START, to=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER) int, long);
+    method @FlaggedApi("android.app.app_start_info") public void addStartInfoTimestamp(@IntRange(from=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START, to=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER) int, long);
     method public void appNotResponding(@NonNull String);
-    method public void clearApplicationStartInfoCompletionListener();
+    method @FlaggedApi("android.app.app_start_info") public void clearApplicationStartInfoCompletionListener();
     method public boolean clearApplicationUserData();
     method public void clearWatchHeapLimit();
     method @RequiresPermission(android.Manifest.permission.DUMP) public void dumpPackageState(java.io.FileDescriptor, String);
@@ -4626,7 +4626,7 @@ package android.app {
     method public java.util.List<android.app.ActivityManager.AppTask> getAppTasks();
     method public android.content.pm.ConfigurationInfo getDeviceConfigurationInfo();
     method @NonNull public java.util.List<android.app.ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String, @IntRange(from=0) int, @IntRange(from=0) int);
-    method @NonNull public java.util.List<android.app.ApplicationStartInfo> getHistoricalProcessStartReasons(@IntRange(from=0) int);
+    method @FlaggedApi("android.app.app_start_info") @NonNull public java.util.List<android.app.ApplicationStartInfo> getHistoricalProcessStartReasons(@IntRange(from=0) int);
     method public int getLargeMemoryClass();
     method public int getLauncherLargeIconDensity();
     method public int getLauncherLargeIconSize();
@@ -4653,7 +4653,7 @@ package android.app {
     method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int);
     method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int, android.os.Bundle);
     method @Deprecated public void restartPackage(String);
-    method public void setApplicationStartInfoCompletionListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>);
+    method @FlaggedApi("android.app.app_start_info") public void setApplicationStartInfoCompletionListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>);
     method public void setProcessStateSummary(@Nullable byte[]);
     method public static void setVrThread(int);
     method public void setWatchHeapLimit(long);
@@ -5234,7 +5234,7 @@ package android.app {
     field public static final int REASON_USER_STOPPED = 11; // 0xb
   }
 
-  public final class ApplicationStartInfo implements android.os.Parcelable {
+  @FlaggedApi("android.app.app_start_info") public final class ApplicationStartInfo implements android.os.Parcelable {
     method public int describeContents();
     method public int getDefiningUid();
     method @Nullable public android.content.Intent getIntent();
@@ -17563,7 +17563,7 @@ package android.graphics.fonts {
     ctor public FontFamily.Builder(@NonNull android.graphics.fonts.Font);
     method @NonNull public android.graphics.fonts.FontFamily.Builder addFont(@NonNull android.graphics.fonts.Font);
     method @NonNull public android.graphics.fonts.FontFamily build();
-    method @Nullable public android.graphics.fonts.FontFamily buildVariableFamily();
+    method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") @Nullable public android.graphics.fonts.FontFamily buildVariableFamily();
   }
 
   public final class FontStyle {
@@ -17656,7 +17656,7 @@ package android.graphics.text {
     method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public int getHyphenation();
     method public int getLineBreakStyle();
     method public int getLineBreakWordStyle();
-    method @NonNull public android.graphics.text.LineBreakConfig merge(@NonNull android.graphics.text.LineBreakConfig);
+    method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.graphics.text.LineBreakConfig merge(@NonNull android.graphics.text.LineBreakConfig);
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_DISABLED = 0; // 0x0
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_ENABLED = 1; // 0x1
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_UNSPECIFIED = -1; // 0xffffffff
@@ -17664,16 +17664,16 @@ package android.graphics.text {
     field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0
     field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2
     field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3
-    field public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1; // 0xffffffff
+    field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1; // 0xffffffff
     field public static final int LINE_BREAK_WORD_STYLE_NONE = 0; // 0x0
     field public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; // 0x1
-    field public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1; // 0xffffffff
+    field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1; // 0xffffffff
   }
 
   public static final class LineBreakConfig.Builder {
     ctor public LineBreakConfig.Builder();
     method @NonNull public android.graphics.text.LineBreakConfig build();
-    method @NonNull public android.graphics.text.LineBreakConfig.Builder merge(@NonNull android.graphics.text.LineBreakConfig);
+    method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.graphics.text.LineBreakConfig.Builder merge(@NonNull android.graphics.text.LineBreakConfig);
     method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.graphics.text.LineBreakConfig.Builder setHyphenation(int);
     method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakStyle(int);
     method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakWordStyle(int);
@@ -17698,7 +17698,7 @@ package android.graphics.text {
     method @NonNull public android.graphics.text.LineBreaker.Builder setHyphenationFrequency(int);
     method @NonNull public android.graphics.text.LineBreaker.Builder setIndents(@Nullable int[]);
     method @NonNull public android.graphics.text.LineBreaker.Builder setJustificationMode(int);
-    method @NonNull public android.graphics.text.LineBreaker.Builder setUseBoundsForWidth(boolean);
+    method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.graphics.text.LineBreaker.Builder setUseBoundsForWidth(boolean);
   }
 
   public static class LineBreaker.ParagraphConstraints {
@@ -17750,18 +17750,18 @@ package android.graphics.text {
     method public float getAdvance();
     method public float getAscent();
     method public float getDescent();
-    method public boolean getFakeBold(@IntRange(from=0) int);
-    method public boolean getFakeItalic(@IntRange(from=0) int);
+    method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public boolean getFakeBold(@IntRange(from=0) int);
+    method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public boolean getFakeItalic(@IntRange(from=0) int);
     method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int);
     method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int);
     method public float getGlyphX(@IntRange(from=0) int);
     method public float getGlyphY(@IntRange(from=0) int);
-    method public float getItalicOverride(@IntRange(from=0) int);
+    method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public float getItalicOverride(@IntRange(from=0) int);
     method public float getOffsetX();
     method public float getOffsetY();
-    method public float getWeightOverride(@IntRange(from=0) int);
+    method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public float getWeightOverride(@IntRange(from=0) int);
     method @IntRange(from=0) public int glyphCount();
-    field public static final float NO_OVERRIDE = 1.4E-45f;
+    field @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public static final float NO_OVERRIDE = 1.4E-45f;
   }
 
   public class TextRunShaper {
@@ -47897,8 +47897,8 @@ package android.text.style {
   }
 
   @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public class LineBreakConfigSpan {
-    ctor public LineBreakConfigSpan(@NonNull android.graphics.text.LineBreakConfig);
-    method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
+    ctor @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public LineBreakConfigSpan(@NonNull android.graphics.text.LineBreakConfig);
+    method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
   }
 
   @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final class LineBreakConfigSpan.NoHyphenationSpan extends android.text.style.LineBreakConfigSpan {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ad658061a0f619b43d7896694492fe18f58ce816..220482da9d64838d8f953067ea983d3fcef2270b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -540,7 +540,7 @@ package android.app {
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int);
     method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void forceStopPackage(String);
     method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.INTERACT_ACROSS_USERS_FULL"}) public static int getCurrentUser();
-    method @NonNull @RequiresPermission(android.Manifest.permission.DUMP) public java.util.List<android.app.ApplicationStartInfo> getExternalHistoricalProcessStartReasons(@NonNull String, @IntRange(from=0) int);
+    method @FlaggedApi(Flags.FLAG_APP_START_INFO) @NonNull @RequiresPermission(android.Manifest.permission.DUMP) public java.util.List<android.app.ApplicationStartInfo> getExternalHistoricalProcessStartReasons(@NonNull String, @IntRange(from=0) int);
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getPackageImportance(String);
     method @NonNull public java.util.Collection<java.util.Locale> getSupportedLocales();
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidImportance(int);
@@ -4529,6 +4529,7 @@ package android.hardware.display {
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String);
     method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float);
+    field public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 128; // 0x80
     field public static final int VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED = 65536; // 0x10000
     field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400
   }
@@ -9842,7 +9843,6 @@ package android.os {
     field public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 1; // 0x1
     field public static final int BUGREPORT_MODE_FULL = 0; // 0x0
     field public static final int BUGREPORT_MODE_INTERACTIVE = 1; // 0x1
-    field public static final int BUGREPORT_MODE_ONBOARDING = 7; // 0x7
     field public static final int BUGREPORT_MODE_REMOTE = 2; // 0x2
     field public static final int BUGREPORT_MODE_TELEPHONY = 4; // 0x4
     field public static final int BUGREPORT_MODE_WEAR = 3; // 0x3
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 4f4569134c1959acc83c2a0d6ba277c65e9c2a4d..3bf2ccaf9923c6e83d4b31acd8007212741d456e 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -24,6 +24,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import android.Manifest;
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -3982,6 +3983,7 @@ public class ActivityManager {
      *         the order from most recent to least recent.
      */
     @NonNull
+    @FlaggedApi(Flags.FLAG_APP_START_INFO)
     public List<ApplicationStartInfo> getHistoricalProcessStartReasons(
             @IntRange(from = 0) int maxNum) {
         try {
@@ -4012,6 +4014,7 @@ public class ActivityManager {
      */
     @NonNull
     @SystemApi
+    @FlaggedApi(Flags.FLAG_APP_START_INFO)
     @RequiresPermission(Manifest.permission.DUMP)
     public List<ApplicationStartInfo> getExternalHistoricalProcessStartReasons(
             @NonNull String packageName, @IntRange(from = 0) int maxNum) {
@@ -4044,6 +4047,7 @@ public class ActivityManager {
      *
      * @throws IllegalArgumentException if executor or listener are null.
      */
+    @FlaggedApi(Flags.FLAG_APP_START_INFO)
     public void setApplicationStartInfoCompletionListener(@NonNull final Executor executor,
             @NonNull final Consumer<ApplicationStartInfo> listener) {
         Preconditions.checkNotNull(executor, "executor cannot be null");
@@ -4065,6 +4069,7 @@ public class ActivityManager {
     /**
      * Removes the callback set by {@link #setApplicationStartInfoCompletionListener} if there is one.
      */
+    @FlaggedApi(Flags.FLAG_APP_START_INFO)
     public void clearApplicationStartInfoCompletionListener() {
         try {
             getService().clearApplicationStartInfoCompleteListener(mContext.getUserId());
@@ -4089,6 +4094,7 @@ public class ActivityManager {
      *                    Will thow {@link java.lang.IllegalArgumentException} if not in range.
      * @param timestampNs Clock monotonic time in nanoseconds of event to be recorded.
      */
+    @FlaggedApi(Flags.FLAG_APP_START_INFO)
     public void addStartInfoTimestamp(@IntRange(
             from = ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START,
             to = ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER) int key,
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 2767b43a119c9d1a521b400c9064d8704ca34d92..6a50e74ca3a4ff35dc6d0556bb2e3192ac19b3c9 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -616,9 +616,8 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
             Log.v(TAG, "getAutofillClient(): null on super, trying to find activity thread");
         }
         // Okay, ppl use the application context when they should not. This breaks
-        // autofill among other things. We pick the focused activity since autofill
-        // interacts only with the currently focused activity and we need the fill
-        // client only if a call comes from the focused activity. Sigh...
+        // autofill among other things. Below is a mitigation to find the top resumed
+        // activity.
         final ActivityThread activityThread = ActivityThread.currentActivityThread();
         if (activityThread == null) {
             return null;
@@ -634,16 +633,27 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
             if (activity == null) {
                 continue;
             }
+            if (record.isTopResumedActivity) {
+                if (android.view.autofill.Helper.sVerbose) {
+                    Log.v(TAG, "getAutofillClient(): found top resumed activity for " + this +
+                            ": " + activity);
+                }
+                return activity.getAutofillClient();
+            }
+            // As a back up option, we pick the focused activity since autofill interacts only
+            // with the currently focused activity and we need the fill client only if a call
+            // comes from the focused activity.
             if (activity.getWindow().getDecorView().hasFocus()) {
                 if (android.view.autofill.Helper.sVerbose) {
-                    Log.v(TAG, "getAutofillClient(): found activity for " + this + ": " + activity);
+                    Log.v(TAG, "getAutofillClient(): found focused activity for " + this +
+                            ": " + activity);
                 }
                 return activity.getAutofillClient();
             }
         }
         if (android.view.autofill.Helper.sVerbose) {
             Log.v(TAG, "getAutofillClient(): none of the " + activityCount + " activities on "
-                    + this + " have focus");
+                    + this + " are top resumed nor have focus");
         }
         return null;
     }
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index f5fb6edfcc04c4108f5de8367e84aafd9795f5b0..a6a57cd5745a0277890529a634b3586d72bd7f4e 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -37,6 +38,7 @@ import java.util.Set;
 /**
  * Provide information related to a processes startup.
  */
+@FlaggedApi(Flags.FLAG_APP_START_INFO)
 public final class ApplicationStartInfo implements Parcelable {
 
     /**
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 6f4abfdc05c1637927a484b73f58c0a9546f6b30..6a03c17159d30aea18c958f4d67832396962cf00 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -207,9 +207,10 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
         final long identity = Binder.clearCallingIdentity();
         try {
             if (rotation == UiAutomation.ROTATION_UNFREEZE) {
-                mWindowManager.thawRotation();
+                mWindowManager.thawRotation(/* caller= */ "UiAutomationConnection#setRotation");
             } else {
-                mWindowManager.freezeRotation(rotation);
+                mWindowManager.freezeRotation(rotation,
+                        /* caller= */ "UiAutomationConnection#setRotation");
             }
             return true;
         } catch (RemoteException re) {
@@ -615,11 +616,13 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
             if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) {
                 // Calling out with a lock held is fine since if the system
                 // process is gone the client calling in will be killed.
-                mWindowManager.freezeRotation(mInitialFrozenRotation);
+                mWindowManager.freezeRotation(mInitialFrozenRotation,
+                        /* caller= */ "UiAutomationConnection#restoreRotationStateLocked");
             } else {
                 // Calling out with a lock held is fine since if the system
                 // process is gone the client calling in will be killed.
-                mWindowManager.thawRotation();
+                mWindowManager.thawRotation(
+                        /* caller= */ "UiAutomationConnection#restoreRotationStateLocked");
             }
         } catch (RemoteException re) {
             /* ignore */
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
new file mode 100644
index 0000000000000000000000000000000000000000..2076e85828a6f4a008fc1f7739f32d9713581d45
--- /dev/null
+++ b/core/java/android/app/activity_manager.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+     namespace: "system_performance"
+     name: "app_start_info"
+     description: "Control collecting of ApplicationStartInfo records and APIs."
+     bug: "247814855"
+}
\ No newline at end of file
diff --git a/core/java/android/database/sqlite/package.html b/core/java/android/database/sqlite/package.html
index 2c366466f9df99015e653a2a9a8e96b13b7d7887..f2df5368494ea2fb726e192d7805da30ae52840d 100644
--- a/core/java/android/database/sqlite/package.html
+++ b/core/java/android/database/sqlite/package.html
@@ -15,7 +15,7 @@ instead use the generic {@link android.database} classes.
 <a href="{@docRoot}studio/command-line/sqlite3.html">sqlite3</a> command-line
 database tool. On your development machine, run the tool from the
 <code>platform-tools/</code> folder of your SDK. On the emulator, run the tool
-with adb shell, for example, <code>adb -e shell sqlite3</code>.
+with adb shell, for example, <code>adb shell sqlite3</code>.
 
 <p>The version of SQLite depends on the version of Android. See the following table:
 <table style="width:auto;">
@@ -42,15 +42,19 @@ with adb shell, for example, <code>adb -e shell sqlite3</code>.
 
 <ul>
   <li>If available, use the sqlite3 tool, for example:
-    <code>adb -e shell sqlite3 --version</code>.</li>
+    <code>adb shell sqlite3 --version</code>.</li>
   <li>Create and query an in-memory database as shown in the following code sample:
     <pre>
     String query = "select sqlite_version() AS sqlite_version";
     SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(":memory:", null);
     Cursor cursor = db.rawQuery(query, null);
     String sqliteVersion = "";
-    if (cursor.moveToNext()) {
-        sqliteVersion = cursor.getString(0);
+    try {
+        if (cursor.moveToNext()) {
+            sqliteVersion = cursor.getString(0);
+        }
+    } finally {
+        cursor.close();
     }</pre>
   </li>
 </ul>
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index f0c87a138a982b4e7d1fa57355d3556f6a4cc5d4..990ebc5fdbcdd6f6143afce8ab6a91a0b0c41661 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -27,6 +27,7 @@ import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -364,11 +365,20 @@ public final class DisplayManager {
 
     /**
      * Virtual display flag: Indicates that the orientation of this display device is coupled to
-     * the rotation of its associated logical display.
+     * the orientation of its associated logical display.
+     * <p>
+     * The flag should not be set when the physical display is mounted in a fixed orientation
+     * such as on a desk. Without this flag, display manager will apply a coordinate transformation
+     * such as a scale and translation to letterbox or pillarbox format under the assumption that
+     * the physical orientation of the display is invariant. With this flag set, the content will
+     * rotate to fill in the space of the display, as it does on the internal device display.
+     * </p>
      *
      * @see #createVirtualDisplay
      * @hide
      */
+    @SuppressLint("UnflaggedApi")
+    @SystemApi
     public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7;
 
     /**
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index ada55325adede58d17d21172db2efa7188117434..f817fb8dcaedc46be06a51d6dbef7202b9c974cf 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -32,7 +32,9 @@ import java.io.FileDescriptor;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -613,15 +615,35 @@ public final class BinderProxy implements IBinder {
      */
     public native boolean transactNative(int code, Parcel data, Parcel reply,
             int flags) throws RemoteException;
+
+    /* This list is to hold strong reference to the death recipients that are waiting for the death
+     * of binder that this proxy references. Previously, the death recipients were strongy
+     * referenced from JNI, but that can cause memory leak (b/298374304) when the application has a
+     * strong reference from the death recipient to the proxy object. The JNI reference is now weak.
+     * And this strong reference is to keep death recipients at least until the proxy is GC'ed. */
+    private List<DeathRecipient> mDeathRecipients = Collections.synchronizedList(new ArrayList<>());
+
     /**
      * See {@link IBinder#linkToDeath(DeathRecipient, int)}
      */
-    public native void linkToDeath(DeathRecipient recipient, int flags)
-            throws RemoteException;
+    public void linkToDeath(DeathRecipient recipient, int flags)
+            throws RemoteException {
+        linkToDeathNative(recipient, flags);
+        mDeathRecipients.add(recipient);
+    }
+
     /**
      * See {@link IBinder#unlinkToDeath}
      */
-    public native boolean unlinkToDeath(DeathRecipient recipient, int flags);
+    public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
+        mDeathRecipients.remove(recipient);
+        return unlinkToDeathNative(recipient, flags);
+    }
+
+    private native void linkToDeathNative(DeathRecipient recipient, int flags)
+            throws RemoteException;
+
+    private native boolean unlinkToDeathNative(DeathRecipient recipient, int flags);
 
     /**
      * Perform a dump on the remote object
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index f10467f0760e449418739cecf1e47ae8a5159f07..47ad72fe8d0e57e2c49a8817a09c9eb892a84db8 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -124,6 +124,8 @@ public final class BugreportParams {
 
     /**
      * Options for a lightweight bugreport intended to be taken for onboarding-related flows.
+     *
+     * @hide
      */
     public static final int BUGREPORT_MODE_ONBOARDING = IDumpstate.BUGREPORT_MODE_ONBOARDING;
 
diff --git a/core/java/android/text/style/LineBreakConfigSpan.java b/core/java/android/text/style/LineBreakConfigSpan.java
index b8033a9d6fe1e84324abbc908643636adc4c25c4..25c1db4d98047bb6d9e9b68dee192c2b5e43f24e 100644
--- a/core/java/android/text/style/LineBreakConfigSpan.java
+++ b/core/java/android/text/style/LineBreakConfigSpan.java
@@ -35,6 +35,7 @@ public class LineBreakConfigSpan {
      * Construct a new {@link LineBreakConfigSpan}
      * @param lineBreakConfig a line break config
      */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public LineBreakConfigSpan(@NonNull LineBreakConfig lineBreakConfig) {
         mLineBreakConfig = lineBreakConfig;
     }
@@ -43,6 +44,7 @@ public class LineBreakConfigSpan {
      * Gets an associated line break config.
      * @return associated line break config.
      */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public @NonNull LineBreakConfig getLineBreakConfig() {
         return mLineBreakConfig;
     }
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index a46136a53e957254c6acc1a36dfdc9c9ba990968..31d759ea92e6262d8d38456d456dde217f357938 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -263,6 +263,16 @@ public abstract class DisplayEventReceiver {
     public void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected) {
     }
 
+    /**
+     * Called when a display hotplug event with connection error is received.
+     *
+     * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()}
+     * timebase.
+     * @param connectionError the hotplug connection error code.
+     */
+    public void onHotplugConnectionError(long timestampNanos, int connectionError) {
+    }
+
     /**
      * Called when a display mode changed event is received.
      *
@@ -345,6 +355,11 @@ public abstract class DisplayEventReceiver {
         onHotplug(timestampNanos, physicalDisplayId, connected);
     }
 
+    @SuppressWarnings("unused")
+    private void dispatchHotplugConnectionError(long timestampNanos, int connectionError) {
+        onHotplugConnectionError(timestampNanos, connectionError);
+    }
+
     // Called from native code.
     @SuppressWarnings("unused")
     private void dispatchModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 512f4f2b5d229908ccb61f5a3ae09df1c8677f00..981911ec88802dbb61932c552e199ca695049838 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.view.Display.Mode.INVALID_MODE_ID;
 import static android.view.DisplayInfoProto.APP_HEIGHT;
 import static android.view.DisplayInfoProto.APP_WIDTH;
 import static android.view.DisplayInfoProto.CUTOUT;
@@ -199,6 +200,11 @@ public final class DisplayInfo implements Parcelable {
      */
     public int defaultModeId;
 
+    /**
+     * The user preferred display mode.
+     */
+    public int userPreferredModeId = INVALID_MODE_ID;
+
     /**
      * The supported modes of this display.
      */
@@ -420,6 +426,7 @@ public final class DisplayInfo implements Parcelable {
                 && modeId == other.modeId
                 && renderFrameRate == other.renderFrameRate
                 && defaultModeId == other.defaultModeId
+                && userPreferredModeId == other.userPreferredModeId
                 && Arrays.equals(supportedModes, other.supportedModes)
                 && colorMode == other.colorMode
                 && Arrays.equals(supportedColorModes, other.supportedColorModes)
@@ -478,6 +485,7 @@ public final class DisplayInfo implements Parcelable {
         modeId = other.modeId;
         renderFrameRate = other.renderFrameRate;
         defaultModeId = other.defaultModeId;
+        userPreferredModeId = other.userPreferredModeId;
         supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length);
         colorMode = other.colorMode;
         supportedColorModes = Arrays.copyOf(
@@ -530,6 +538,7 @@ public final class DisplayInfo implements Parcelable {
         modeId = source.readInt();
         renderFrameRate = source.readFloat();
         defaultModeId = source.readInt();
+        userPreferredModeId = source.readInt();
         int nModes = source.readInt();
         supportedModes = new Display.Mode[nModes];
         for (int i = 0; i < nModes; i++) {
@@ -596,6 +605,7 @@ public final class DisplayInfo implements Parcelable {
         dest.writeInt(modeId);
         dest.writeFloat(renderFrameRate);
         dest.writeInt(defaultModeId);
+        dest.writeInt(userPreferredModeId);
         dest.writeInt(supportedModes.length);
         for (int i = 0; i < supportedModes.length; i++) {
             supportedModes[i].writeToParcel(dest, flags);
@@ -832,9 +842,12 @@ public final class DisplayInfo implements Parcelable {
         sb.append(presentationDeadlineNanos);
         sb.append(", mode ");
         sb.append(modeId);
+        sb.append(", renderFrameRate ");
         sb.append(renderFrameRate);
         sb.append(", defaultMode ");
         sb.append(defaultModeId);
+        sb.append(", userPreferredModeId ");
+        sb.append(userPreferredModeId);
         sb.append(", modes ");
         sb.append(Arrays.toString(supportedModes));
         sb.append(", hdrCapabilities ");
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index d3b7a5be47baa587b634c95dc0889a68507545a0..cccac95b9caaa9b28800c96f5b15dbd065df7f98 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -316,14 +316,14 @@ interface IWindowManager
      * android.view.Display#DEFAULT_DISPLAY} and given rotation.
      */
     @UnsupportedAppUsage
-    void freezeRotation(int rotation);
+    void freezeRotation(int rotation, String caller);
 
     /**
      * Equivalent to calling {@link #thawDisplayRotation(int)} with {@link
      * android.view.Display#DEFAULT_DISPLAY}.
      */
     @UnsupportedAppUsage
-    void thawRotation();
+    void thawRotation(String caller);
 
     /**
      * Equivelant to call {@link #isDisplayRotationFrozen(int)} with {@link
@@ -341,7 +341,7 @@ interface IWindowManager
      *        {@link android.view.Surface#ROTATION_270} or -1 to freeze it to current rotation.
      * @hide
      */
-    void freezeDisplayRotation(int displayId, int rotation);
+    void freezeDisplayRotation(int displayId, int rotation, String caller);
 
     /**
      * Release the orientation lock imposed by freezeRotation() on the display.
@@ -349,7 +349,7 @@ interface IWindowManager
      * @param displayId the ID of display which rotation should be thawed.
      * @hide
      */
-    void thawDisplayRotation(int displayId);
+    void thawDisplayRotation(int displayId, String caller);
 
     /**
      * Gets whether the rotation is frozen on the display.
diff --git a/core/java/com/android/internal/view/RotationPolicy.java b/core/java/com/android/internal/view/RotationPolicy.java
index 058c6ec4d13c7b63ab0859e3cce75145b14a8fc9..6e45796df053a7de65cf4173d92eaa0905d58355 100644
--- a/core/java/com/android/internal/view/RotationPolicy.java
+++ b/core/java/com/android/internal/view/RotationPolicy.java
@@ -105,23 +105,23 @@ public final class RotationPolicy {
     /**
      * Enables or disables rotation lock from the system UI toggle.
      */
-    public static void setRotationLock(Context context, final boolean enabled) {
+    public static void setRotationLock(Context context, final boolean enabled, String caller) {
         final int rotation = areAllRotationsAllowed(context)
                 || useCurrentRotationOnRotationLockChange(context) ? CURRENT_ROTATION
                 : NATURAL_ROTATION;
-        setRotationLockAtAngle(context, enabled, rotation);
+        setRotationLockAtAngle(context, enabled, rotation, caller);
     }
 
     /**
      * Enables or disables rotation lock at a specific rotation from system UI.
      */
     public static void setRotationLockAtAngle(Context context, final boolean enabled,
-            final int rotation) {
+            final int rotation, String caller) {
         Settings.System.putIntForUser(context.getContentResolver(),
                 Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0,
                 UserHandle.USER_CURRENT);
 
-        setRotationLock(enabled, rotation);
+        setRotationLock(enabled, rotation, caller);
     }
 
     /**
@@ -129,12 +129,13 @@ public final class RotationPolicy {
      *
      * If rotation is locked for accessibility, the system UI toggle is hidden to avoid confusion.
      */
-    public static void setRotationLockForAccessibility(Context context, final boolean enabled) {
+    public static void setRotationLockForAccessibility(Context context, final boolean enabled,
+            String caller) {
         Settings.System.putIntForUser(context.getContentResolver(),
                 Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, enabled ? 1 : 0,
                         UserHandle.USER_CURRENT);
 
-        setRotationLock(enabled, NATURAL_ROTATION);
+        setRotationLock(enabled, NATURAL_ROTATION, caller);
     }
 
     private static boolean areAllRotationsAllowed(Context context) {
@@ -146,16 +147,17 @@ public final class RotationPolicy {
                 R.bool.config_useCurrentRotationOnRotationLockChange);
     }
 
-    private static void setRotationLock(final boolean enabled, final int rotation) {
+    private static void setRotationLock(final boolean enabled, final int rotation,
+            final String caller) {
         AsyncTask.execute(new Runnable() {
             @Override
             public void run() {
                 try {
                     IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
                     if (enabled) {
-                        wm.freezeRotation(rotation);
+                        wm.freezeRotation(rotation, caller);
                     } else {
-                        wm.thawRotation();
+                        wm.thawRotation(caller);
                     }
                 } catch (RemoteException exc) {
                     Log.w(TAG, "Unable to save auto-rotate setting");
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index ad196c014decad4e87bfb144eafc43117273dc4f..9ed41553a4b43df79b6febbaf2fad06acfbcc002 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -15,7 +15,19 @@ license {
     ],
 }
 
-cc_library_shared {
+soong_config_module_type {
+    name: "cc_library_shared_for_libandroid_runtime",
+    module_type: "cc_library_shared",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "release_binder_death_recipient_weak_from_jni",
+    ],
+    properties: [
+        "cflags",
+    ],
+}
+
+cc_library_shared_for_libandroid_runtime {
     name: "libandroid_runtime",
     host_supported: true,
     cflags: [
@@ -46,6 +58,12 @@ cc_library_shared {
         },
     },
 
+    soong_config_variables: {
+        release_binder_death_recipient_weak_from_jni: {
+            cflags: ["-DBINDER_DEATH_RECIPIENT_WEAK_FROM_JNI"],
+        },
+    },
+
     cpp_std: "gnu++20",
 
     srcs: [
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 041f9c7edeefc6ef22809358d74372e900ecaa1c..bfd80a9e4f741ccb1a0765e7cace57458fbd33f5 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -17,19 +17,8 @@
 #define LOG_TAG "JavaBinder"
 //#define LOG_NDEBUG 0
 
-#include "android_os_Parcel.h"
 #include "android_util_Binder.h"
 
-#include <atomic>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <mutex>
-#include <stdio.h>
-#include <string>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
 #include <android-base/stringprintf.h>
 #include <binder/BpBinder.h>
 #include <binder/IInterface.h>
@@ -40,7 +29,16 @@
 #include <binder/Stability.h>
 #include <binderthreadstate/CallerUtils.h>
 #include <cutils/atomic.h>
+#include <fcntl.h>
+#include <inttypes.h>
 #include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
 #include <utils/KeyedVector.h>
 #include <utils/List.h>
 #include <utils/Log.h>
@@ -48,10 +46,11 @@
 #include <utils/SystemClock.h>
 #include <utils/threads.h>
 
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedLocalRef.h>
-#include <nativehelper/ScopedUtfChars.h>
+#include <atomic>
+#include <mutex>
+#include <string>
 
+#include "android_os_Parcel.h"
 #include "core_jni_helpers.h"
 
 //#undef ALOGV
@@ -553,14 +552,48 @@ public:
 };
 
 // ----------------------------------------------------------------------------
+#ifdef BINDER_DEATH_RECIPIENT_WEAK_FROM_JNI
+#if __BIONIC__
+#include <android/api-level.h>
+static bool target_sdk_is_at_least_vic() {
+    return android_get_application_target_sdk_version() >= __ANDROID_API_V__;
+}
+#else
+static constexpr bool target_sdk_is_at_least_vic() {
+    // If not built for Android (i.e. glibc host), follow the latest behavior as there's no compat
+    // requirement there.
+    return true;
+}
+#endif // __BIONIC__
+#endif // BINDER_DEATH_RECIPIENT_WEAK_FROM_JNI
 
 class JavaDeathRecipient : public IBinder::DeathRecipient
 {
 public:
     JavaDeathRecipient(JNIEnv* env, jobject object, const sp<DeathRecipientList>& list)
-        : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object)),
-          mObjectWeak(NULL), mList(list)
-    {
+          : mVM(jnienv_to_javavm(env)), mObject(NULL), mObjectWeak(NULL), mList(list) {
+        // b/298374304: For apps targeting Android V or beyond, we no longer hold the global JNI ref
+        // to the death recipient objects. This is to prevent the memory leak which can happen when
+        // the death recipient object internally has a strong reference to the proxy object. Under
+        // the old behavior, you were unable to kill the binder service by dropping all references
+        // to the proxy object - because it is still strong referenced from JNI (here). The only way
+        // to cut the strong reference was to call unlinkDeath(), but it was easy to forget.
+        //
+        // Now, the strong reference to the death recipient is held in the Java-side proxy object.
+        // See BinderProxy.mDeathRecipients. From JNI, only the weak reference is kept. An
+        // implication of this is that you may not receive binderDied() if you drop all references
+        // to the proxy object before the service dies. This should be okay for most cases because
+        // you normally are not interested in the death of a binder service which you don't have any
+        // reference to. If however you want to get binderDied() regardless of the proxy object's
+        // lifecycle, keep a strong reference to the death recipient object by yourself.
+#ifdef BINDER_DEATH_RECIPIENT_WEAK_FROM_JNI
+        if (target_sdk_is_at_least_vic()) {
+            mObjectWeak = env->NewWeakGlobalRef(object);
+        } else
+#endif
+        {
+            mObject = env->NewGlobalRef(object);
+        }
         // These objects manage their own lifetimes so are responsible for final bookkeeping.
         // The list holds a strong reference to this object.
         LOGDEATH("Adding JDR %p to DRL %p", this, list.get());
@@ -573,26 +606,49 @@ public:
     void binderDied(const wp<IBinder>& who)
     {
         LOGDEATH("Receiving binderDied() on JavaDeathRecipient %p\n", this);
-        if (mObject != NULL) {
-            JNIEnv* env = javavm_to_jnienv(mVM);
-            ScopedLocalRef<jobject> jBinderProxy(env, javaObjectForIBinder(env, who.promote()));
-            env->CallStaticVoidMethod(gBinderProxyOffsets.mClass,
-                                      gBinderProxyOffsets.mSendDeathNotice, mObject,
-                                      jBinderProxy.get());
-            if (env->ExceptionCheck()) {
-                jthrowable excep = env->ExceptionOccurred();
-                binder_report_exception(env, excep,
-                                        "*** Uncaught exception returned from death notification!");
-            }
+        if (mObject == NULL && mObjectWeak == NULL) {
+            return;
+        }
+        JNIEnv* env = javavm_to_jnienv(mVM);
+        ScopedLocalRef<jobject> jBinderProxy(env, javaObjectForIBinder(env, who.promote()));
+
+        // Hold a local reference to the recipient. This may fail if the recipient is weakly
+        // referenced, in which case we can't deliver the death notice.
+        ScopedLocalRef<jobject> jRecipient(env,
+                                           env->NewLocalRef(mObject != NULL ? mObject
+                                                                            : mObjectWeak));
+        if (jRecipient.get() == NULL) {
+            ALOGW("Binder died, but death recipient is already garbage collected. If your target "
+                  "sdk level is at or above 35, this can happen when you dropped all references to "
+                  "the binder service before it died. If you want to get a death notice for a "
+                  "binder service which you have no reference to, keep a strong reference to the "
+                  "death recipient by yourself.");
+            return;
+        }
 
-            // Serialize with our containing DeathRecipientList so that we can't
-            // delete the global ref on mObject while the list is being iterated.
+        if (mFired) {
+            ALOGW("Received multiple death notices for the same binder object. Binder driver bug?");
+            return;
+        }
+        mFired = true;
+
+        env->CallStaticVoidMethod(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mSendDeathNotice,
+                                  jRecipient.get(), jBinderProxy.get());
+        if (env->ExceptionCheck()) {
+            jthrowable excep = env->ExceptionOccurred();
+            binder_report_exception(env, excep,
+                                    "*** Uncaught exception returned from death notification!");
+        }
+
+        // Demote from strong ref (if exists) to weak after binderDied() has been delivered, to
+        // allow the DeathRecipient and BinderProxy to be GC'd if no longer needed. Do this in sync
+        // with our containing DeathRecipientList so that we can't delete the global ref on mObject
+        // while the list is being iterated.
+        if (mObject != NULL) {
             sp<DeathRecipientList> list = mList.promote();
             if (list != NULL) {
                 AutoMutex _l(list->lock());
 
-                // Demote from strong ref to weak after binderDied() has been delivered,
-                // to allow the DeathRecipient and BinderProxy to be GC'd if no longer needed.
                 mObjectWeak = env->NewWeakGlobalRef(mObject);
                 env->DeleteGlobalRef(mObject);
                 mObject = NULL;
@@ -659,9 +715,19 @@ protected:
 
 private:
     JavaVM* const mVM;
-    jobject mObject;  // Initial strong ref to Java-side DeathRecipient. Cleared on binderDied().
-    jweak mObjectWeak; // Weak ref to the same Java-side DeathRecipient after binderDied().
+
+    // If target sdk version < 35, the Java-side DeathRecipient is strongly referenced from mObject
+    // upon linkToDeath() and then after binderDied() is called, the strong reference is demoted to
+    // a weak reference (mObjectWeak).
+    // If target sdk version >= 35, the strong reference is never made here (i.e. mObject == NULL
+    // always). Instead, the strong reference to the Java-side DeathRecipient is made in
+    // BinderProxy.mDeathRecipients. In the native world, only the weak reference is kept.
+    jobject mObject;
+    jweak mObjectWeak;
     wp<DeathRecipientList> mList;
+
+    // Whether binderDied was called or not.
+    bool mFired = false;
 };
 
 // ----------------------------------------------------------------------------
@@ -1435,17 +1501,19 @@ static jobject android_os_BinderProxy_getExtension(JNIEnv* env, jobject obj) {
 
 // ----------------------------------------------------------------------------
 
+// clang-format off
 static const JNINativeMethod gBinderProxyMethods[] = {
      /* name, signature, funcPtr */
     {"pingBinder",          "()Z", (void*)android_os_BinderProxy_pingBinder},
     {"isBinderAlive",       "()Z", (void*)android_os_BinderProxy_isBinderAlive},
     {"getInterfaceDescriptor", "()Ljava/lang/String;", (void*)android_os_BinderProxy_getInterfaceDescriptor},
     {"transactNative",      "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact},
-    {"linkToDeath",         "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath},
-    {"unlinkToDeath",       "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath},
+    {"linkToDeathNative",   "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath},
+    {"unlinkToDeathNative", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath},
     {"getNativeFinalizer",  "()J", (void*)android_os_BinderProxy_getNativeFinalizer},
     {"getExtension",        "()Landroid/os/IBinder;", (void*)android_os_BinderProxy_getExtension},
 };
+// clang-format on
 
 const char* const kBinderProxyPathName = "android/os/BinderProxy";
 
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 69fc515444b289c6f697d84fe43110fb86091cfc..41c65aec3144df268392945a9cd37c912addb6db 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -38,6 +38,7 @@ static struct {
 
     jmethodID dispatchVsync;
     jmethodID dispatchHotplug;
+    jmethodID dispatchHotplugConnectionError;
     jmethodID dispatchModeChanged;
     jmethodID dispatchFrameRateOverrides;
 
@@ -89,6 +90,7 @@ private:
     void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
                        VsyncEventData vsyncEventData) override;
     void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
+    void dispatchHotplugConnectionError(nsecs_t timestamp, int errorCode) override;
     void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
                              nsecs_t renderPeriod) override;
     void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
@@ -230,6 +232,22 @@ void NativeDisplayEventReceiver::dispatchHotplug(nsecs_t timestamp, PhysicalDisp
     mMessageQueue->raiseAndClearException(env, "dispatchHotplug");
 }
 
+void NativeDisplayEventReceiver::dispatchHotplugConnectionError(nsecs_t timestamp,
+                                                                int connectionError) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+    ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal));
+    if (receiverObj.get()) {
+        ALOGV("receiver %p ~ Invoking hotplug dispatchHotplugConnectionError handler.", this);
+        env->CallVoidMethod(receiverObj.get(),
+                            gDisplayEventReceiverClassInfo.dispatchHotplugConnectionError,
+                            timestamp, connectionError);
+        ALOGV("receiver %p ~ Returned from hotplug dispatchHotplugConnectionError handler.", this);
+    }
+
+    mMessageQueue->raiseAndClearException(env, "dispatchHotplugConnectionError");
+}
+
 void NativeDisplayEventReceiver::dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId,
                                                      int32_t modeId, nsecs_t renderPeriod) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -354,8 +372,12 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) {
 
     gDisplayEventReceiverClassInfo.dispatchVsync =
             GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JJI)V");
-    gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env,
-            gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
+    gDisplayEventReceiverClassInfo.dispatchHotplug =
+            GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug",
+                             "(JJZ)V");
+    gDisplayEventReceiverClassInfo.dispatchHotplugConnectionError =
+            GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz,
+                             "dispatchHotplugConnectionError", "(JI)V");
     gDisplayEventReceiverClassInfo.dispatchModeChanged =
             GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchModeChanged",
                              "(JJIJ)V");
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3d0af3dbde3d53920d5ad146e9347371dd9bcb14..c00a776fc3da1749749f69eda3527948baf6a769 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5227,6 +5227,28 @@
          non-zero. -->
     <integer name="config_defaultPeakRefreshRate">0</integer>
 
+    <!-- External display peak refresh rate for the given device. Change this value if you want to
+         prevent the framework from using higher refresh rates, even if display modes with higher
+         refresh rates are available from hardware composer. Only has an effect if this value and
+         config_externalDisplayPeakWidth and config_externalDisplayPeakHeight are non-zero. -->
+    <integer name="config_externalDisplayPeakRefreshRate">0</integer>
+
+    <!-- External display peak width for the given device. Change this value if you want
+         to prevent the framework from using higher resolution, even if display modes with higher
+         resolutions are available from hardware composer. Only has an effect if this value and
+         config_externalDisplayPeakRefreshRate and config_externalDisplayPeakHeight are non-zero.-->
+    <integer name="config_externalDisplayPeakWidth">0</integer>
+
+    <!-- External display peak height for the given device. Change this value if you want
+         to prevent the framework from using higher resolution, even if display modes with higher
+         resolutions are available from hardware composer. Only has an effect if this value and
+         config_externalDisplayPeakRefreshRate and config_externalDisplayPeakWidth are non-zero. -->
+    <integer name="config_externalDisplayPeakHeight">0</integer>
+
+    <!-- Enable synchronization of the displays refresh rates by applying the default low refresh
+         rate. -->
+    <bool name="config_refreshRateSynchronizationEnabled">false</bool>
+
     <!-- The display uses different gamma curves for different refresh rates. It's hard for panel
          vendors to tune the curves to have exact same brightness for different refresh rate. So
          flicker could be observed at switch time. The issue is worse at the gamma lower end.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 83fb0986a19fa34b57211a340129267b10ce7e31..b0eee1cecc89864f0b0cda14d59c8c1cd0e13e81 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4231,6 +4231,10 @@
   <!-- For high refresh rate displays -->
   <java-symbol type="integer" name="config_defaultRefreshRate" />
   <java-symbol type="integer" name="config_defaultPeakRefreshRate" />
+  <java-symbol type="integer" name="config_externalDisplayPeakRefreshRate" />
+  <java-symbol type="integer" name="config_externalDisplayPeakWidth" />
+  <java-symbol type="integer" name="config_externalDisplayPeakHeight" />
+  <java-symbol type="bool" name="config_refreshRateSynchronizationEnabled" />
   <java-symbol type="integer" name="config_defaultRefreshRateInZone" />
   <java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" />
   <java-symbol type="array" name="config_ambientThresholdsOfPeakRefreshRate" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index ad0ead78f49216d094b3c0a9eae59da00884392f..9c6528785584d7e896992558147ff15c6a18183a 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1123,12 +1123,6 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
     },
-    "-1076978367": {
-      "message": "thawRotation: mRotation=%d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ORIENTATION",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "-1075136930": {
       "message": "startLockTaskMode: Can't lock due to auth",
       "level": "WARN",
@@ -1231,6 +1225,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-962760979": {
+      "message": "thawRotation: mRotation=%d, caller=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "-961053385": {
       "message": "attachWindowContextToDisplayArea: calling from non-existing process pid=%d uid=%d",
       "level": "WARN",
@@ -2779,6 +2779,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "364992694": {
+      "message": "freezeDisplayRotation: current rotation=%d, new rotation=%d, caller=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "371173718": {
       "message": "finishSync cancel=%b for %s",
       "level": "VERBOSE",
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
index 3e645791903170c96953f7308cdc19c85625f6cb..78d257f86613330656860d3bb2566c28ab0a063d 100644
--- a/graphics/java/android/graphics/RuntimeShader.java
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -49,11 +49,11 @@ import libcore.util.NativeAllocationRegistry;
  *  possible antialiasing logic for border pixels).</li>
  *  <li>Logic for the {@link Shader}, {@link ColorFilter}, and {@link BlendMode} on the
  *  {@link Paint}.</li>
- *  <li>Color space conversion code, as part of Android’s color management.</li>
+ *  <li>Color space conversion code, as part of Android's color management.</li>
  * </ul>
  *
  * <p>A {@link RuntimeShader}, like other {@link Shader} types, effectively contributes a function
- * to the GPU’s fragment shader.</p>
+ * to the GPU's fragment shader.</p>
  *
  * <h3>AGSL Shader Execution</h3>
  * <p>Just like a GLSL shader, an AGSL shader begins execution in a main function. Unlike GLSL, the
@@ -78,10 +78,10 @@ import libcore.util.NativeAllocationRegistry;
  * {@link ColorSpace} for an AGSL shader is defined to be the color space of the destination, which
  * in most cases is determined by {@link Window#setColorMode(int)}.</p>
  *
- * <p>When authoring an AGSL shader, you won’t know what the working color space is. For many
+ * <p>When authoring an AGSL shader, you won't know what the working color space is. For many
  * effects, this is fine because by default color inputs are automatically converted into the
  * working color space. For certain effects, it may be important to do some math in a fixed, known
- * color space. A common example is lighting – to get physically accurate lighting, math should be
+ * color space. A common example is lighting - to get physically accurate lighting, math should be
  * done in a linear color space. To help with this, AGSL provides two intrinsic functions that
  * convert colors between the working color space and the
  * {@link ColorSpace.Named#LINEAR_EXTENDED_SRGB} color space:
@@ -93,7 +93,7 @@ import libcore.util.NativeAllocationRegistry;
  * <h3>AGSL and Premultiplied Alpha</h3>
  * <p>When dealing with transparent colors, there are two (common) possible representations:
  * straight (unassociated) alpha and premultiplied (associated) alpha. In ASGL the color returned
- * by the main function is expected to be premultiplied.  AGSL’s use of premultiplied alpha
+ * by the main function is expected to be premultiplied.  AGSL's use of premultiplied alpha
  * implies:
  * </p>
  *
@@ -101,7 +101,7 @@ import libcore.util.NativeAllocationRegistry;
  *  <li>If your AGSL shader will return transparent colors, be sure to multiply the RGB by A.  The
  *  resulting color should be [R*A, G*A, B*A, A], not [R, G, B, A].</li>
  *  <li>For more complex shaders, you must understand which of your colors are premultiplied vs.
- *  straight. Many operations don’t make sense if you mix both kinds of color together.</li>
+ *  straight. Many operations don't make sense if you mix both kinds of color together.</li>
  * </ul>
  *
  * <h3>Uniforms</h3>
@@ -224,7 +224,7 @@ import libcore.util.NativeAllocationRegistry;
  * shader uniform is undefined if it is declared in the AGSL shader but not initialized.</p>
  *
  * <p>Although most {@link BitmapShader}s contain colors that should be color managed, some contain
- * data that isn’t actually colors. This includes bitmaps storing normals, material properties
+ * data that isn't actually colors. This includes bitmaps storing normals, material properties
  * (e.g. roughness), heightmaps, or any other purely mathematical data that happens to be stored in
  * a bitmap. When using these kinds of shaders in AGSL, you probably want to initialize them with
  * {@link #setInputBuffer(String, BitmapShader)}. Shaders initialized this way work much like
@@ -237,7 +237,7 @@ import libcore.util.NativeAllocationRegistry;
  *
  * <p>In addition, when sampling from a {@link BitmapShader} be aware that the shader does not use
  * normalized coordinates (like a texture in GLSL). It uses (0, 0) in the upper-left corner, and
- * (width, height) in the bottom-right corner. Normally, this is exactly what you want. If you’re
+ * (width, height) in the bottom-right corner. Normally, this is exactly what you want. If you're
  * evaluating the shader with coordinates based on the ones passed to your AGSL program, the scale
  * is correct. However, if you want to adjust those coordinates (to do some kind of re-mapping of
  * the bitmap), remember that the coordinates are local to the canvas.</p>
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index 7cca7f19da7ddb4739742db0df97efe8905b6c7c..5e41105903254cf05c07e6e21b47895fd926b16c 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -16,6 +16,9 @@
 
 package android.graphics.fonts;
 
+import static com.android.text.flags.Flags.FLAG_DEPRECATE_FONTS_XML;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -138,6 +141,7 @@ public final class FontFamily {
          * @return A variable font family. null if a variable font cannot be built from the given
          *         fonts.
          */
+        @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
         public @Nullable FontFamily buildVariableFamily() {
             int variableFamilyType = analyzeAndResolveVariableType(mFonts);
             if (variableFamilyType == VARIABLE_FONT_FAMILY_TYPE_UNKNOWN) {
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 13540e0df7892f5f5b283c5746746781df747ea8..e81525fb7d60408ec4843b03ed6bd486662dd75c 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -110,6 +110,7 @@ public final class LineBreakConfig {
      * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if this value is used for text
      * layout/rendering.
      */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1;
 
     /**
@@ -165,6 +166,7 @@ public final class LineBreakConfig {
      * This value is resolved to {@link #LINE_BREAK_WORD_STYLE_NONE} if this value is used for
      * text layout/rendering.
      */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1;
 
     /**
@@ -236,6 +238,7 @@ public final class LineBreakConfig {
          * @param config an override line break config
          * @return This {@code Builder}.
          */
+        @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
         public @NonNull Builder merge(@NonNull LineBreakConfig config) {
             if (config.mLineBreakStyle != LINE_BREAK_STYLE_UNSPECIFIED) {
                 mLineBreakStyle = config.mLineBreakStyle;
@@ -483,6 +486,7 @@ public final class LineBreakConfig {
      * @param config an overriding config.
      * @return newly created instance that is current style merged with passed config.
      */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public @NonNull LineBreakConfig merge(@NonNull LineBreakConfig config) {
         return new LineBreakConfig(
                 config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index 34ab8338fb6fdc93c7df6441f2f4a53f6083b999..dc2e794a1df444c60572f0f63ab238188edc4f87 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -16,6 +16,9 @@
 
 package android.graphics.text;
 
+import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
+
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -248,6 +251,7 @@ public class LineBreaker {
          * @see Layout#getUseBoundsForWidth()
          * @see StaticLayout.Builder#setUseBoundsForWidth(boolean)
          */
+        @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
         public @NonNull Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
             mUseBoundsForWidth = useBoundsForWidth;
             return this;
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index 49e9d0cf83721304749c9a7dd7b0ea7e857527bb..569f9b6fe945f572d0b808921e06ff7d3e92d1e8 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -16,6 +16,9 @@
 
 package android.graphics.text;
 
+import static com.android.text.flags.Flags.FLAG_DEPRECATE_FONTS_XML;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.graphics.Paint;
@@ -170,6 +173,7 @@ public final class PositionedGlyphs {
      * @param index the glyph index
      * @return true if the fake bold option is on, otherwise off.
      */
+    @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
     public boolean getFakeBold(@IntRange(from = 0) int index) {
         Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
         return nGetFakeBold(mLayoutPtr, index);
@@ -181,6 +185,7 @@ public final class PositionedGlyphs {
      * @param index the glyph index
      * @return true if the fake italic option is on, otherwise off.
      */
+    @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
     public boolean getFakeItalic(@IntRange(from = 0) int index) {
         Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
         return nGetFakeItalic(mLayoutPtr, index);
@@ -190,6 +195,7 @@ public final class PositionedGlyphs {
      * A special value returned by {@link #getWeightOverride(int)} and
      * {@link #getItalicOverride(int)} that indicates no font variation setting is overridden.
      */
+    @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
     public static final float NO_OVERRIDE = Float.MIN_VALUE;
 
     /**
@@ -199,6 +205,7 @@ public final class PositionedGlyphs {
      * @param index the glyph index
      * @return overridden weight value or {@link #NO_OVERRIDE}.
      */
+    @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
     public float getWeightOverride(@IntRange(from = 0) int index) {
         Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
         float value = nGetWeightOverride(mLayoutPtr, index);
@@ -216,6 +223,7 @@ public final class PositionedGlyphs {
      * @param index the glyph index
      * @return overridden weight value or {@link #NO_OVERRIDE}.
      */
+    @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
     public float getItalicOverride(@IntRange(from = 0) int index) {
         Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
         float value = nGetItalicOverride(mLayoutPtr, index);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
index 5eeb3b650074455924ac6eee79049ca2a9f0b38c..b141bebbe8b111b1fbe42d51800cd49143d68832 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
@@ -18,12 +18,16 @@ package com.android.wm.shell.compatui;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.annotation.IdRes;
 import android.annotation.NonNull;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
 import android.widget.TextView;
@@ -36,14 +40,29 @@ import com.android.wm.shell.R;
  */
 public class UserAspectRatioSettingsLayout extends LinearLayout {
 
+    private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+
+    private static final Interpolator PATH_INTERPOLATOR =
+            new PathInterpolator(0.2f, 0f, 0f, 1f);
+
     private static final float ALPHA_FULL_TRANSPARENT = 0f;
 
     private static final float ALPHA_FULL_OPAQUE = 1f;
 
-    private static final long VISIBILITY_ANIMATION_DURATION_MS = 50;
+    private static final float SCALE_START = 0.8f;
+
+    private static final float SCALE_END = 1f;
+
+    private static final long FADE_ANIMATION_DURATION_MS = 167;
+
+    private static final long SCALE_ANIMATION_DURATION_MS = 300;
 
     private static final String ALPHA_PROPERTY_NAME = "alpha";
 
+    private static final String SCALE_X_PROPERTY_NAME = "scaleX";
+
+    private static final String SCALE_Y_PROPERTY_NAME = "scaleY";
+
     private UserAspectRatioSettingsWindowManager mWindowManager;
 
     public UserAspectRatioSettingsLayout(Context context) {
@@ -88,7 +107,7 @@ public class UserAspectRatioSettingsLayout extends LinearLayout {
         if (show) {
             showItem(view);
         } else {
-            view.setVisibility(visibility);
+            hideItem(view);
         }
     }
 
@@ -121,16 +140,40 @@ public class UserAspectRatioSettingsLayout extends LinearLayout {
     }
 
     private void showItem(@NonNull View view) {
-        view.setVisibility(View.VISIBLE);
+        final AnimatorSet animatorSet = new AnimatorSet();
         final ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME,
                 ALPHA_FULL_TRANSPARENT, ALPHA_FULL_OPAQUE);
-        fadeIn.setDuration(VISIBILITY_ANIMATION_DURATION_MS);
-        fadeIn.addListener(new AnimatorListenerAdapter() {
+        fadeIn.setDuration(FADE_ANIMATION_DURATION_MS);
+        fadeIn.setInterpolator(LINEAR_INTERPOLATOR);
+        final ObjectAnimator scaleY =
+                ObjectAnimator.ofFloat(view, SCALE_Y_PROPERTY_NAME, SCALE_START, SCALE_END);
+        final ObjectAnimator scaleX =
+                ObjectAnimator.ofFloat(view, SCALE_X_PROPERTY_NAME, SCALE_START, SCALE_END);
+        scaleX.setDuration(SCALE_ANIMATION_DURATION_MS);
+        scaleX.setInterpolator(PATH_INTERPOLATOR);
+        scaleY.setDuration(SCALE_ANIMATION_DURATION_MS);
+        scaleY.setInterpolator(PATH_INTERPOLATOR);
+        animatorSet.addListener(new AnimatorListenerAdapter() {
             @Override
-            public void onAnimationEnd(Animator animation) {
+            public void onAnimationStart(Animator animation) {
                 view.setVisibility(View.VISIBLE);
             }
         });
-        fadeIn.start();
+        animatorSet.playTogether(fadeIn, scaleY, scaleX);
+        animatorSet.start();
+    }
+
+    private void hideItem(@NonNull View view) {
+        final ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME,
+                ALPHA_FULL_OPAQUE, ALPHA_FULL_TRANSPARENT);
+        fadeOut.setDuration(FADE_ANIMATION_DURATION_MS);
+        fadeOut.setInterpolator(LINEAR_INTERPOLATOR);
+        fadeOut.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                view.setVisibility(View.GONE);
+            }
+        });
+        fadeOut.start();
     }
 }
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
index 764d1efcc8f405eff542a135b10002282357f8fa..69fda34afc784dcd4f22c4f95d5199d3c04224bc 100644
--- a/libs/hwui/Mesh.h
+++ b/libs/hwui/Mesh.h
@@ -166,11 +166,12 @@ public:
 #endif
                 mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
                                             ib, mIndexCount, mIndexOffset, mBuilder->fUniforms,
-                                            mBounds)
+                                            SkSpan<SkRuntimeEffect::ChildPtr>(), mBounds)
                                 .mesh;
             } else {
                 mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
-                                     mBuilder->fUniforms, mBounds)
+                                     mBuilder->fUniforms, SkSpan<SkRuntimeEffect::ChildPtr>(),
+                                     mBounds)
                                 .mesh;
             }
             mIsDirty = false;
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 71f47e92e05510492772ab5c89e3da1bc1a10a36..ff0d8d74831cfa86dcca6e91e5797e867ea563e1 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -539,7 +539,7 @@ struct DrawSkMesh final : Op {
             if (!cpuMesh.indexBuffer()) {
                 gpuMesh = SkMesh::Make(cpuMesh.refSpec(), cpuMesh.mode(), vb, cpuMesh.vertexCount(),
                                        cpuMesh.vertexOffset(), cpuMesh.refUniforms(),
-                                       cpuMesh.bounds())
+                                       SkSpan<SkRuntimeEffect::ChildPtr>(), cpuMesh.bounds())
                                   .mesh;
             } else {
                 sk_sp<SkMesh::IndexBuffer> ib =
@@ -547,7 +547,8 @@ struct DrawSkMesh final : Op {
                 gpuMesh = SkMesh::MakeIndexed(cpuMesh.refSpec(), cpuMesh.mode(), vb,
                                               cpuMesh.vertexCount(), cpuMesh.vertexOffset(), ib,
                                               cpuMesh.indexCount(), cpuMesh.indexOffset(),
-                                              cpuMesh.refUniforms(), cpuMesh.bounds())
+                                              cpuMesh.refUniforms(),
+                                              SkSpan<SkRuntimeEffect::ChildPtr>(), cpuMesh.bounds())
                                   .mesh;
             }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 943c2b4b3a5b860f273e5a45a42323210a93c41d..ba88484518aabe77f44638ecccf7b5480780e77b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -16,23 +16,183 @@
 
 package com.android.credentialmanager.autofill
 
+import android.app.assist.AssistStructure
+import android.content.Context
+import android.credentials.GetCredentialRequest
+import android.credentials.CredentialManager
+import android.credentials.GetCandidateCredentialsResponse
+import android.credentials.CredentialOption
+import android.credentials.GetCandidateCredentialsException
+import android.os.Bundle
 import android.os.CancellationSignal
+import android.os.OutcomeReceiver
+import android.service.autofill.FillRequest
 import android.service.autofill.AutofillService
+import android.service.autofill.FillResponse
 import android.service.autofill.FillCallback
-import android.service.autofill.FillRequest
 import android.service.autofill.SaveRequest
 import android.service.autofill.SaveCallback
+import android.util.Log
+import org.json.JSONObject
+import java.util.concurrent.Executors
 
 class CredentialAutofillService : AutofillService() {
+
+    companion object {
+        private const val TAG = "CredAutofill"
+
+        private const val CRED_HINT_PREFIX = "credential="
+        private const val REQUEST_DATA_KEY = "requestData"
+        private const val CANDIDATE_DATA_KEY = "candidateQueryData"
+        private const val SYS_PROVIDER_REQ_KEY = "isSystemProviderRequired"
+        private const val CRED_OPTIONS_KEY = "credentialOptions"
+        private const val TYPE_KEY = "type"
+    }
+
+    private val credentialManager: CredentialManager =
+            getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
+
     override fun onFillRequest(
             request: FillRequest,
             cancellationSignal: CancellationSignal,
             callback: FillCallback
     ) {
+        val context = request.fillContexts
+        val structure = context[context.size - 1].structure
+        val callingPackage = structure.activityComponent.packageName
+        Log.i(TAG, "onFillRequest called for $callingPackage")
+
+        val getCredRequest: GetCredentialRequest? = getCredManRequest(structure)
+        if (getCredRequest == null) {
+            callback.onFailure("No credential manager request found")
+            return
+        }
+
+        val outcome = object : OutcomeReceiver<GetCandidateCredentialsResponse,
+                GetCandidateCredentialsException> {
+            override fun onResult(result: GetCandidateCredentialsResponse) {
+                Log.i(TAG, "getCandidateCredentials onResponse")
+                val fillResponse: FillResponse? = convertToFillResponse(result, request)
+                callback.onSuccess(fillResponse)
+            }
+
+            override fun onError(error: GetCandidateCredentialsException) {
+                Log.i(TAG, "getCandidateCredentials onError")
+                callback.onFailure("error received from credential manager ${error.message}")
+            }
+        }
+
+        credentialManager.getCandidateCredentials(
+                getCredRequest,
+                callingPackage,
+                CancellationSignal(),
+                Executors.newSingleThreadExecutor(),
+                outcome
+        )
+    }
+
+    private fun convertToFillResponse(
+            getCredResponse: GetCandidateCredentialsResponse,
+            filLRequest: FillRequest
+    ): FillResponse? {
         TODO("Not yet implemented")
     }
 
     override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
         TODO("Not yet implemented")
     }
+
+    private fun getCredManRequest(structure: AssistStructure): GetCredentialRequest? {
+        val credentialOptions: MutableList<CredentialOption> = mutableListOf()
+        traverseStructure(structure, credentialOptions)
+
+        if (credentialOptions.isNotEmpty()) {
+            return GetCredentialRequest.Builder(Bundle.EMPTY)
+                    .setCredentialOptions(credentialOptions)
+                    .build()
+        }
+        return null
+    }
+
+    private fun traverseStructure(
+            structure: AssistStructure,
+            cmRequests: MutableList<CredentialOption>
+    ) {
+        val windowNodes: List<AssistStructure.WindowNode> =
+                structure.run {
+                    (0 until windowNodeCount).map { getWindowNodeAt(it) }
+                }
+
+        windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
+            traverseNode(windowNode.rootViewNode, cmRequests)
+        }
+    }
+
+    private fun traverseNode(
+            viewNode: AssistStructure.ViewNode?,
+            cmRequests: MutableList<CredentialOption>
+    ) {
+        val options = getCredentialOptionsFromViewNode(viewNode)
+        cmRequests.addAll(options)
+
+        val children: List<AssistStructure.ViewNode>? =
+                viewNode?.run {
+                    (0 until childCount).map { getChildAt(it) }
+                }
+
+        children?.forEach { childNode: AssistStructure.ViewNode ->
+            traverseNode(childNode, cmRequests)
+        }
+    }
+
+    private fun getCredentialOptionsFromViewNode(viewNode: AssistStructure.ViewNode?):
+            List<CredentialOption> {
+        // TODO(b/293945193) Replace with isCredential check from viewNode
+        val credentialHints: MutableList<String> = mutableListOf()
+        if (viewNode != null && viewNode.autofillHints != null) {
+            for (hint in viewNode.autofillHints!!) {
+                if (hint.startsWith(CRED_HINT_PREFIX)) {
+                    credentialHints.add(hint.substringAfter(CRED_HINT_PREFIX))
+                }
+            }
+        }
+
+        val credentialOptions: MutableList<CredentialOption> = mutableListOf()
+        for (credentialHint in credentialHints) {
+            convertJsonToCredentialOption(credentialHint).let { credentialOptions.addAll(it) }
+        }
+        return credentialOptions
+    }
+
+    private fun convertJsonToCredentialOption(jsonString: String): List<CredentialOption> {
+        // TODO(b/302000646) Move this logic to jetpack so that is consistent
+        //  with building the json
+        val credentialOptions: MutableList<CredentialOption> = mutableListOf()
+
+        val json = JSONObject(jsonString)
+        val options = json.getJSONArray(CRED_OPTIONS_KEY)
+        for (i in 0 until options.length()) {
+            val option = options.getJSONObject(i)
+
+            credentialOptions.add(CredentialOption(
+                    option.getString(TYPE_KEY),
+                    convertJsonToBundle(option.getJSONObject(REQUEST_DATA_KEY)),
+                    convertJsonToBundle(option.getJSONObject(CANDIDATE_DATA_KEY)),
+                    option.getBoolean(SYS_PROVIDER_REQ_KEY),
+            ))
+        }
+        return credentialOptions
+    }
+
+    private fun convertJsonToBundle(json: JSONObject): Bundle {
+        val result = Bundle()
+        json.keys().forEach {
+            val v = json.get(it)
+            when (v) {
+                is String -> result.putString(it, v)
+                is Boolean -> result.putBoolean(it, v)
+            }
+        }
+        return result
+    }
 }
\ No newline at end of file
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 5dcb9d2f6e778505ef857990a3a3f5bdb728602b..2d231f2f55daca25724236a927b30803df5f6380 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -71,7 +71,6 @@ android_library {
         "SettingsLibMainSwitchPreference",
         "SettingsLibProfileSelector",
         "SettingsLibProgressBar",
-        "SettingsLibRadioButtonPreference",
         "SettingsLibRestrictedLockUtils",
         "SettingsLibSelectorWithWidgetPreference",
         "SettingsLibSettingsSpinner",
diff --git a/packages/SettingsLib/RadioButtonPreference/Android.bp b/packages/SettingsLib/RadioButtonPreference/Android.bp
deleted file mode 100644
index 505ba05c5f59b91bdbad0a0630641bc6d376e9fa..0000000000000000000000000000000000000000
--- a/packages/SettingsLib/RadioButtonPreference/Android.bp
+++ /dev/null
@@ -1,29 +0,0 @@
-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"],
-}
-
-android_library {
-    name: "SettingsLibRadioButtonPreference",
-    use_resource_processor: true,
-
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
-
-    static_libs: [
-          "androidx.preference_preference",
-          "SettingsLibSelectorWithWidgetPreference",
-          "SettingsLibSettingsTheme",
-    ],
-
-    sdk_version: "system_current",
-    min_sdk_version: "21",
-    apex_available: [
-        "//apex_available:platform",
-        "com.android.permission",
-    ],
-}
diff --git a/packages/SettingsLib/RadioButtonPreference/AndroidManifest.xml b/packages/SettingsLib/RadioButtonPreference/AndroidManifest.xml
deleted file mode 100644
index 8b5c3b1b5652f1f361afc85c100604845b2df47d..0000000000000000000000000000000000000000
--- a/packages/SettingsLib/RadioButtonPreference/AndroidManifest.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2019 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.settingslib.widget.preference.radio">
-
-    <uses-sdk android:minSdkVersion="21" />
-
-</manifest>
diff --git a/packages/SettingsLib/RadioButtonPreference/res/drawable/ic_settings_accent.xml b/packages/SettingsLib/RadioButtonPreference/res/drawable/ic_settings_accent.xml
deleted file mode 100644
index 6521bc9e0fb9d33d5315926a7ad899b74caa6324..0000000000000000000000000000000000000000
--- a/packages/SettingsLib/RadioButtonPreference/res/drawable/ic_settings_accent.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<!--
-  Copyright (C) 2021 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="?android:attr/colorAccent">
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M13.85,22.25h-3.7c-0.74,0 -1.36,-0.54 -1.45,-1.27l-0.27,-1.89c-0.27,-0.14 -0.53,-0.29 -0.79,-0.46l-1.8,0.72c-0.7,0.26 -1.47,-0.03 -1.81,-0.65L2.2,15.53c-0.35,-0.66 -0.2,-1.44 0.36,-1.88l1.53,-1.19c-0.01,-0.15 -0.02,-0.3 -0.02,-0.46c0,-0.15 0.01,-0.31 0.02,-0.46l-1.52,-1.19C1.98,9.9 1.83,9.09 2.2,8.47l1.85,-3.19c0.34,-0.62 1.11,-0.9 1.79,-0.63l1.81,0.73c0.26,-0.17 0.52,-0.32 0.78,-0.46l0.27,-1.91c0.09,-0.7 0.71,-1.25 1.44,-1.25h3.7c0.74,0 1.36,0.54 1.45,1.27l0.27,1.89c0.27,0.14 0.53,0.29 0.79,0.46l1.8,-0.72c0.71,-0.26 1.48,0.03 1.82,0.65l1.84,3.18c0.36,0.66 0.2,1.44 -0.36,1.88l-1.52,1.19c0.01,0.15 0.02,0.3 0.02,0.46s-0.01,0.31 -0.02,0.46l1.52,1.19c0.56,0.45 0.72,1.23 0.37,1.86l-1.86,3.22c-0.34,0.62 -1.11,0.9 -1.8,0.63l-1.8,-0.72c-0.26,0.17 -0.52,0.32 -0.78,0.46l-0.27,1.91C15.21,21.71 14.59,22.25 13.85,22.25zM13.32,20.72c0,0.01 0,0.01 0,0.02L13.32,20.72zM10.68,20.7l0,0.02C10.69,20.72 10.69,20.71 10.68,20.7zM10.62,20.25h2.76l0.37,-2.55l0.53,-0.22c0.44,-0.18 0.88,-0.44 1.34,-0.78l0.45,-0.34l2.38,0.96l1.38,-2.4l-2.03,-1.58l0.07,-0.56c0.03,-0.26 0.06,-0.51 0.06,-0.78c0,-0.27 -0.03,-0.53 -0.06,-0.78l-0.07,-0.56l2.03,-1.58l-1.39,-2.4l-2.39,0.96l-0.45,-0.35c-0.42,-0.32 -0.87,-0.58 -1.33,-0.77L13.75,6.3l-0.37,-2.55h-2.76L10.25,6.3L9.72,6.51C9.28,6.7 8.84,6.95 8.38,7.3L7.93,7.63L5.55,6.68L4.16,9.07l2.03,1.58l-0.07,0.56C6.09,11.47 6.06,11.74 6.06,12c0,0.26 0.02,0.53 0.06,0.78l0.07,0.56l-2.03,1.58l1.38,2.4l2.39,-0.96l0.45,0.35c0.43,0.33 0.86,0.58 1.33,0.77l0.53,0.22L10.62,20.25zM18.22,17.72c0,0.01 -0.01,0.02 -0.01,0.03L18.22,17.72zM5.77,17.71l0.01,0.02C5.78,17.72 5.77,17.71 5.77,17.71zM3.93,9.47L3.93,9.47C3.93,9.47 3.93,9.47 3.93,9.47zM18.22,6.27c0,0.01 0.01,0.02 0.01,0.02L18.22,6.27zM5.79,6.25L5.78,6.27C5.78,6.27 5.79,6.26 5.79,6.25zM13.31,3.28c0,0.01 0,0.01 0,0.02L13.31,3.28zM10.69,3.26l0,0.02C10.69,3.27 10.69,3.27 10.69,3.26z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M12,12m-3.5,0a3.5,3.5 0,1 1,7 0a3.5,3.5 0,1 1,-7 0"/>
-</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/RadioButtonPreference/res/layout-v33/preference_radio.xml b/packages/SettingsLib/RadioButtonPreference/res/layout-v33/preference_radio.xml
deleted file mode 100644
index bb8ac286e38dc2c634a13db3607a202fbdae7613..0000000000000000000000000000000000000000
--- a/packages/SettingsLib/RadioButtonPreference/res/layout-v33/preference_radio.xml
+++ /dev/null
@@ -1,127 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  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.
-  -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:settings="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:background="?android:attr/selectableItemBackground"
-    android:gravity="center_vertical"
-    android:minHeight="?android:attr/listPreferredItemHeightSmall"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
-
-    <LinearLayout
-        android:id="@android:id/widget_frame"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:paddingHorizontal="20dp"
-        android:gravity="center"
-        android:minWidth="56dp"
-        android:orientation="vertical"/>
-
-    <LinearLayout
-        android:id="@+id/icon_frame"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:gravity="center_vertical"
-        android:minWidth="32dp"
-        android:orientation="horizontal"
-        android:layout_marginEnd="16dp"
-        android:paddingTop="4dp"
-        android:paddingBottom="4dp">
-        <androidx.preference.internal.PreferenceImageView
-            android:id="@android:id/icon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            settings:maxWidth="@dimen/secondary_app_icon_size"
-            settings:maxHeight="@dimen/secondary_app_icon_size"/>
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:orientation="vertical"
-        android:paddingTop="16dp"
-        android:paddingBottom="16dp"
-        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
-
-        <TextView
-            android:id="@android:id/title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:maxLines="2"
-            android:hyphenationFrequency="normalFast"
-            android:lineBreakWordStyle="phrase"
-            android:textAppearance="?android:attr/textAppearanceListItem"/>
-
-        <LinearLayout
-            android:id="@+id/summary_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:visibility="gone">
-            <TextView
-                android:id="@android:id/summary"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:textAppearance="?android:attr/textAppearanceSmall"
-                android:textAlignment="viewStart"
-                android:hyphenationFrequency="normalFast"
-                android:lineBreakWordStyle="phrase"
-                android:textColor="?android:attr/textColorSecondary"/>
-
-            <TextView
-                android:id="@+id/appendix"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:textAppearance="?android:attr/textAppearanceSmall"
-                android:textAlignment="viewEnd"
-                android:textColor="?android:attr/textColorSecondary"
-                android:maxLines="1"
-                android:visibility="gone"
-                android:ellipsize="end"/>
-        </LinearLayout>
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/radio_extra_widget_container"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:orientation="horizontal"
-        android:gravity="center_vertical">
-        <View
-            android:layout_width=".75dp"
-            android:layout_height="32dp"
-            android:layout_marginTop="16dp"
-            android:layout_marginBottom="16dp"
-            android:background="?android:attr/dividerVertical" />
-        <ImageView
-            android:id="@+id/radio_extra_widget"
-            android:layout_width="match_parent"
-            android:minWidth="@dimen/two_target_min_width"
-            android:layout_height="fill_parent"
-            android:src="@drawable/ic_settings_accent"
-            android:contentDescription="@string/settings_label"
-            android:paddingStart="24dp"
-            android:paddingEnd="24dp"
-            android:layout_gravity="center"
-            android:background="?android:attr/selectableItemBackground" />
-    </LinearLayout>
-</LinearLayout>
diff --git a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
deleted file mode 100644
index 906ff2cc09d513f7d993df3b22142bf9f1796142..0000000000000000000000000000000000000000
--- a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2019 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:settings="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:background="?android:attr/selectableItemBackground"
-    android:gravity="center_vertical"
-    android:minHeight="?android:attr/listPreferredItemHeightSmall"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
-
-    <LinearLayout
-        android:id="@android:id/widget_frame"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:paddingHorizontal="20dp"
-        android:gravity="center"
-        android:minWidth="56dp"
-        android:orientation="vertical"/>
-
-    <LinearLayout
-        android:id="@+id/icon_frame"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:gravity="center_vertical"
-        android:minWidth="32dp"
-        android:orientation="horizontal"
-        android:layout_marginEnd="16dp"
-        android:paddingTop="4dp"
-        android:paddingBottom="4dp">
-        <androidx.preference.internal.PreferenceImageView
-            android:id="@android:id/icon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            settings:maxWidth="@dimen/secondary_app_icon_size"
-            settings:maxHeight="@dimen/secondary_app_icon_size"/>
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:orientation="vertical"
-        android:paddingTop="16dp"
-        android:paddingBottom="16dp"
-        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
-
-        <TextView
-            android:id="@android:id/title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:maxLines="2"
-            android:textAppearance="?android:attr/textAppearanceListItem"/>
-
-        <LinearLayout
-            android:id="@+id/summary_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:visibility="gone">
-            <TextView
-                android:id="@android:id/summary"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:textAppearance="?android:attr/textAppearanceSmall"
-                android:textAlignment="viewStart"
-                android:textColor="?android:attr/textColorSecondary"/>
-
-            <TextView
-                android:id="@+id/appendix"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:textAppearance="?android:attr/textAppearanceSmall"
-                android:textAlignment="viewEnd"
-                android:textColor="?android:attr/textColorSecondary"
-                android:maxLines="1"
-                android:visibility="gone"
-                android:ellipsize="end"/>
-        </LinearLayout>
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/radio_extra_widget_container"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:orientation="horizontal"
-        android:gravity="center_vertical">
-        <View
-            android:layout_width=".75dp"
-            android:layout_height="32dp"
-            android:layout_marginTop="16dp"
-            android:layout_marginBottom="16dp"
-            android:background="?android:attr/dividerVertical" />
-        <ImageView
-            android:id="@+id/radio_extra_widget"
-            android:layout_width="match_parent"
-            android:minWidth="@dimen/two_target_min_width"
-            android:layout_height="fill_parent"
-            android:src="@drawable/ic_settings_accent"
-            android:contentDescription="@string/settings_label"
-            android:paddingStart="24dp"
-            android:paddingEnd="24dp"
-            android:layout_gravity="center"
-            android:background="?android:attr/selectableItemBackground" />
-    </LinearLayout>
-</LinearLayout>
diff --git a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_widget_radiobutton.xml b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_widget_radiobutton.xml
deleted file mode 100644
index cb7b8eb289eaee490e88239a963cd3bfbfcd29fc..0000000000000000000000000000000000000000
--- a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_widget_radiobutton.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2019 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/checkbox"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:background="@null"
-    android:focusable="false"
-    android:clickable="false" />
diff --git a/packages/SettingsLib/RadioButtonPreference/res/values/strings.xml b/packages/SettingsLib/RadioButtonPreference/res/values/strings.xml
deleted file mode 100644
index ff3f90cfffd3e5462561e185d2cabaa88d5ea167..0000000000000000000000000000000000000000
--- a/packages/SettingsLib/RadioButtonPreference/res/values/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2021 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Content description for RadioButton with extra gear icon [CHAR LIMIT=NONE] -->
-    <string name="settings_label">Settings</string>
-
-</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/RadioButtonPreference/src/com/android/settingslib/widget/RadioButtonPreference.java b/packages/SettingsLib/RadioButtonPreference/src/com/android/settingslib/widget/RadioButtonPreference.java
deleted file mode 100644
index fc4b71430c5f27ab6769b4820b72d791c3d385d6..0000000000000000000000000000000000000000
--- a/packages/SettingsLib/RadioButtonPreference/src/com/android/settingslib/widget/RadioButtonPreference.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.widget;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.ImageView;
-
-import androidx.preference.CheckBoxPreference;
-import androidx.preference.PreferenceViewHolder;
-
-import com.android.settingslib.widget.preference.radio.R;
-
-/**
- * DEPRECATED. Please use SelectorWithWidgetPreference instead.
- *
- * This file has been moved there and will be removed once all callers are updated.
- *
- * Check box preference with check box replaced by radio button.
- *
- * Functionally speaking, it's actually a CheckBoxPreference. We only modified
- * the widget to RadioButton to make it "look like" a RadioButtonPreference.
- *
- * In other words, there's no "RadioButtonPreferenceGroup" in this
- * implementation. When you check one RadioButtonPreference, if you want to
- * uncheck all the other preferences, you should do that by code yourself.
- *
- * RadioButtonPreference can assign a extraWidgetListener to show a gear icon
- * on the right side that can open another page.
- *
- * @Deprecated
- */
-public class RadioButtonPreference extends CheckBoxPreference {
-
-    /**
-     * Interface definition for a callback to be invoked when the preference is clicked.
-     */
-    public interface OnClickListener {
-        /**
-         * Called when a preference has been clicked.
-         *
-         * @param emiter The clicked preference
-         */
-        void onRadioButtonClicked(RadioButtonPreference emiter);
-    }
-
-    private OnClickListener mListener = null;
-    private View mAppendix;
-    private int mAppendixVisibility = -1;
-
-    private View mExtraWidgetContainer;
-    private ImageView mExtraWidget;
-
-    private View.OnClickListener mExtraWidgetOnClickListener;
-
-    /**
-     * Perform inflation from XML and apply a class-specific base style.
-     *
-     * @param context  The {@link Context} this is associated with, through which it can
-     *                 access the current theme, resources, {@link SharedPreferences}, etc.
-     * @param attrs    The attributes of the XML tag that is inflating the preference
-     * @param defStyle An attribute in the current theme that contains a reference to a style
-     *                 resource that supplies default values for the view. Can be 0 to not
-     *                 look for defaults.
-     */
-    public RadioButtonPreference(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        init();
-    }
-
-    /**
-     * Perform inflation from XML and apply a class-specific base style.
-     *
-     * @param context The {@link Context} this is associated with, through which it can
-     *                access the current theme, resources, {@link SharedPreferences}, etc.
-     * @param attrs   The attributes of the XML tag that is inflating the preference
-     */
-    public RadioButtonPreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init();
-    }
-
-    /**
-     * Constructor to create a preference.
-     *
-     * @param context The Context this is associated with.
-     */
-    public RadioButtonPreference(Context context) {
-        this(context, null);
-    }
-
-    /**
-     * Sets the callback to be invoked when this preference is clicked by the user.
-     *
-     * @param listener The callback to be invoked
-     */
-    public void setOnClickListener(OnClickListener listener) {
-        mListener = listener;
-    }
-
-    /**
-     * Processes a click on the preference.
-     */
-    @Override
-    public void onClick() {
-        if (mListener != null) {
-            mListener.onRadioButtonClicked(this);
-        }
-    }
-
-    /**
-     * Binds the created View to the data for this preference.
-     *
-     * <p>This is a good place to grab references to custom Views in the layout and set
-     * properties on them.
-     *
-     * <p>Make sure to call through to the superclass's implementation.
-     *
-     * @param holder The ViewHolder that provides references to the views to fill in. These views
-     *               will be recycled, so you should not hold a reference to them after this method
-     *               returns.
-     */
-    @Override
-    public void onBindViewHolder(PreferenceViewHolder holder) {
-        super.onBindViewHolder(holder);
-
-        View summaryContainer = holder.findViewById(R.id.summary_container);
-        if (summaryContainer != null) {
-            summaryContainer.setVisibility(
-                    TextUtils.isEmpty(getSummary()) ? View.GONE : View.VISIBLE);
-            mAppendix = holder.findViewById(R.id.appendix);
-            if (mAppendix != null && mAppendixVisibility != -1) {
-                mAppendix.setVisibility(mAppendixVisibility);
-            }
-        }
-
-        mExtraWidget = (ImageView) holder.findViewById(R.id.radio_extra_widget);
-        mExtraWidgetContainer = holder.findViewById(R.id.radio_extra_widget_container);
-
-        setExtraWidgetOnClickListener(mExtraWidgetOnClickListener);
-    }
-
-    /**
-     * Set the visibility state of appendix view.
-     *
-     * @param visibility One of {@link View#VISIBLE}, {@link View#INVISIBLE}, or {@link View#GONE}.
-     */
-    public void setAppendixVisibility(int visibility) {
-        if (mAppendix != null) {
-            mAppendix.setVisibility(visibility);
-        }
-        mAppendixVisibility = visibility;
-    }
-
-    /**
-     * Sets the callback to be invoked when extra widget is clicked by the user.
-     *
-     * @param listener The callback to be invoked
-     */
-    public void setExtraWidgetOnClickListener(View.OnClickListener listener) {
-        mExtraWidgetOnClickListener = listener;
-
-        if (mExtraWidget == null || mExtraWidgetContainer == null) {
-            return;
-        }
-
-        mExtraWidget.setOnClickListener(mExtraWidgetOnClickListener);
-
-        mExtraWidgetContainer.setVisibility((mExtraWidgetOnClickListener != null)
-                ? View.VISIBLE : View.GONE);
-    }
-
-    private void init() {
-        setWidgetLayoutResource(R.layout.preference_widget_radiobutton);
-        setLayoutResource(R.layout.preference_radio);
-        setIconSpaceReserved(false);
-    }
-}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index 1a938d6ec37e86f5127c41e6490aac3528a403a4..a4089c0d8697f88ebb5e2f6a839dc7969a6a0592 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -22,6 +22,7 @@ import static com.android.settingslib.drawer.TileUtils.META_DATA_NEW_TASK;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SEARCHABLE;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
@@ -423,6 +424,13 @@ public abstract class Tile implements Parcelable {
         return (mMetaData == null) ? null : mMetaData.getString(META_DATA_PREFERENCE_GROUP_KEY);
     }
 
+    /**
+     * Returns if this is searchable.
+     */
+    public boolean isSearchable() {
+        return mMetaData == null || mMetaData.getBoolean(META_DATA_PREFERENCE_SEARCHABLE, true);
+    }
+
     /**
      * The type of the tile.
      */
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index 33907ece5a7c37dbbf6c63b08d90b678ad784d2f..d0929e19bc9b53c0ac70d77afec9690bdbc4a72e 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -249,6 +249,11 @@ public class TileUtils {
      */
     public static final String META_DATA_NEW_TASK = "com.android.settings.new_task";
 
+    /**
+     * If the entry should be shown in settings search results. Defaults to true.
+     */
+    public static final String META_DATA_PREFERENCE_SEARCHABLE = "com.android.settings.searchable";
+
     /**
      * Build a list of DashboardCategory.
      */
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
index 4d2b1ae2ade0293edfdefe71e0044d73e2a54d0a..21cdc492e4ea406eb7af31d0a2710a004cdafde4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
@@ -20,6 +20,7 @@ import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SEARCHABLE;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
@@ -256,4 +257,26 @@ public class ActivityTileTest {
 
         assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH_WITH_ACTION);
     }
+
+    @Test
+    public void isSearchable_noMetadata_isTrue() {
+        final Tile tile = new ActivityTile(null, "category");
+
+        assertThat(tile.isSearchable()).isTrue();
+    }
+
+    @Test
+    public void isSearchable_notSet_isTrue() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.isSearchable()).isTrue();
+    }
+
+    @Test
+    public void isSearchable_isSet_false() {
+        mActivityInfo.metaData.putBoolean(META_DATA_PREFERENCE_SEARCHABLE, false);
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.isSearchable()).isFalse();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
index 80f9efb8b5accd302e30010a5285e947d8415f4c..faccf2f15f4918190f0895378ef9d5f98f299d3a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
@@ -20,6 +20,7 @@ import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SEARCHABLE;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
@@ -257,6 +258,28 @@ public class ProviderTileTest {
         assertThat(tile.getType()).isEqualTo(Tile.Type.GROUP);
     }
 
+    @Test
+    public void isSearchable_noMetadata_isTrue() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", null);
+
+        assertThat(tile.isSearchable()).isTrue();
+    }
+
+    @Test
+    public void isSearchable_notSet_isTrue() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.isSearchable()).isTrue();
+    }
+
+    @Test
+    public void isSearchable_isSet_false() {
+        mMetaData.putBoolean(META_DATA_PREFERENCE_SEARCHABLE, false);
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.isSearchable()).isFalse();
+    }
+
     @Implements(TileUtils.class)
     private static class ShadowTileUtils {
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/RadioButtonPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/RadioButtonPreferenceTest.java
deleted file mode 100644
index 0f708cdb9992386fd74dd75e42512981ac07e58b..0000000000000000000000000000000000000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/RadioButtonPreferenceTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.widget;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.app.Application;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import androidx.preference.PreferenceViewHolder;
-
-import com.android.settingslib.widget.preference.radio.R;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-@RunWith(RobolectricTestRunner.class)
-public class RadioButtonPreferenceTest {
-
-    private Application mContext;
-    private RadioButtonPreference mPreference;
-
-    private View mExtraWidgetContainer;
-    private View mExtraWidget;
-
-    private boolean mIsClickListenerCalled;
-    private View.OnClickListener mClickListener = new View.OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            mIsClickListenerCalled = true;
-        }
-    };
-
-    @Before
-    public void setUp() {
-        mContext = RuntimeEnvironment.application;
-        mPreference = new RadioButtonPreference(mContext);
-
-        View view = LayoutInflater.from(mContext)
-                .inflate(R.layout.preference_radio, null /* root */);
-        PreferenceViewHolder preferenceViewHolder =
-                PreferenceViewHolder.createInstanceForTests(view);
-        mPreference.onBindViewHolder(preferenceViewHolder);
-
-        mExtraWidgetContainer = view.findViewById(R.id.radio_extra_widget_container);
-        mExtraWidget = view.findViewById(R.id.radio_extra_widget);
-    }
-
-    @Test
-    public void shouldHaveRadioPreferenceLayout() {
-        assertThat(mPreference.getLayoutResource()).isEqualTo(R.layout.preference_radio);
-    }
-
-    @Test
-    public void iconSpaceReservedShouldBeFalse() {
-        assertThat(mPreference.isIconSpaceReserved()).isFalse();
-    }
-
-    @Test
-    public void onBindViewHolder_withSummary_containerShouldBeVisible() {
-        mPreference.setSummary("some summary");
-        View summaryContainer = new View(mContext);
-        View view = mock(View.class);
-        when(view.findViewById(R.id.summary_container)).thenReturn(summaryContainer);
-        PreferenceViewHolder preferenceViewHolder =
-                PreferenceViewHolder.createInstanceForTests(view);
-
-        mPreference.onBindViewHolder(preferenceViewHolder);
-
-        assertEquals(View.VISIBLE, summaryContainer.getVisibility());
-    }
-
-    @Test
-    public void onBindViewHolder_emptySummary_containerShouldBeGone() {
-        mPreference.setSummary("");
-        View summaryContainer = new View(mContext);
-        View view = mock(View.class);
-        when(view.findViewById(R.id.summary_container)).thenReturn(summaryContainer);
-        PreferenceViewHolder preferenceViewHolder =
-                PreferenceViewHolder.createInstanceForTests(view);
-
-        mPreference.onBindViewHolder(preferenceViewHolder);
-
-        assertEquals(View.GONE, summaryContainer.getVisibility());
-    }
-
-    @Test
-    public void nullSummary_containerShouldBeGone() {
-        mPreference.setSummary(null);
-        View summaryContainer = new View(mContext);
-        View view = mock(View.class);
-        when(view.findViewById(R.id.summary_container)).thenReturn(summaryContainer);
-        PreferenceViewHolder preferenceViewHolder =
-                PreferenceViewHolder.createInstanceForTests(view);
-        mPreference.onBindViewHolder(preferenceViewHolder);
-        assertEquals(View.GONE, summaryContainer.getVisibility());
-    }
-
-    @Test
-    public void setAppendixVisibility_setGone_shouldBeGone() {
-        mPreference.setAppendixVisibility(View.GONE);
-
-        View view = LayoutInflater.from(mContext)
-                .inflate(R.layout.preference_radio, null /* root */);
-        PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view);
-        mPreference.onBindViewHolder(holder);
-        assertThat(holder.findViewById(R.id.appendix).getVisibility()).isEqualTo(View.GONE);
-    }
-
-    @Test
-    public void setExtraWidgetListener_setNull_extraWidgetShouldInvisible() {
-        mPreference.setExtraWidgetOnClickListener(null);
-
-        assertEquals(View.GONE, mExtraWidgetContainer.getVisibility());
-    }
-
-    @Test
-    public void setExtraWidgetListener_extraWidgetShouldVisible() {
-        mPreference.setExtraWidgetOnClickListener(mClickListener);
-
-        assertEquals(View.VISIBLE, mExtraWidgetContainer.getVisibility());
-    }
-
-    @Test
-    public void onClickListener_setExtraWidgetOnClickListener_ShouldCalled() {
-        mPreference.setExtraWidgetOnClickListener(mClickListener);
-
-        assertThat(mIsClickListenerCalled).isFalse();
-        mExtraWidget.callOnClick();
-        assertThat(mIsClickListenerCalled).isTrue();
-    }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 46a9d42d0606d9b361e51b8e09bef4de40af6367..d0f2ce84655d41abd9307bbd0a053e1aa46c94d0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -48,13 +48,13 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
-import com.android.systemui.res.R
 import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -104,7 +104,8 @@ private fun SceneScope.BouncerScene(
     modifier: Modifier = Modifier,
 ) {
     val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
-    val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethod.collectAsState()
+    val authMethodViewModel: AuthMethodBouncerViewModel? by
+        viewModel.authMethodViewModel.collectAsState()
     val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
     var dialog: Dialog? by remember { mutableStateOf(null) }
     val backgroundColor = MaterialTheme.colorScheme.surface
diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar.xml b/packages/SystemUI/res/drawable/volume_row_seekbar.xml
index 7ce1ba3be7f633805817615eb2119477db8e7a19..d7d75d4304d0ae4b4ef85715a0b1117956f0f850 100644
--- a/packages/SystemUI/res/drawable/volume_row_seekbar.xml
+++ b/packages/SystemUI/res/drawable/volume_row_seekbar.xml
@@ -20,17 +20,14 @@
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:paddingMode="stack" >
+    <!-- The groove used for indicating max volume !-->
     <item android:id="@android:id/background"
         android:gravity="center_vertical|fill_horizontal">
-        <inset
-            android:insetLeft="@dimen/rounded_slider_track_inset"
-            android:insetRight="@dimen/rounded_slider_track_inset" >
-            <shape>
-                <size android:height="@dimen/volume_dialog_track_width" />
-                <corners android:radius="@dimen/volume_dialog_track_corner_radius" />
-                <solid android:color="?androidprv:attr/colorAccentSecondaryVariant" />
-            </shape>
-        </inset>
+        <shape>
+            <size android:height="@dimen/volume_dialog_track_width" />
+            <corners android:radius="@dimen/volume_dialog_panel_width_half" />
+            <solid android:color="?androidprv:attr/materialColorOutlineVariant" />
+        </shape>
     </item>
     <item android:id="@android:id/progress"
         android:gravity="center_vertical|fill_horizontal">
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 88726af39c25c2f173aa47ac6cd2e4ddf7595e6c..d5658dcf0f8ee8e7276a6698fb58a67eb7c67bad 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -545,7 +545,7 @@
     <!-- (volume_dialog_panel_width - rounded_slider_icon_size) / 2 -->
     <dimen name="volume_slider_icon_inset">11dp</dimen>
 
-    <dimen name="volume_dialog_track_width">4dp</dimen>
+    <dimen name="volume_dialog_track_width">40dp</dimen>
 
     <dimen name="volume_dialog_track_corner_radius">2dp</dimen>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 905923039f8b6796e31c7222d82db3799c9d8cb6..c0749885846f9e398f4dfb6b34f2325db0823119 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -282,9 +282,9 @@ public class RotationButtonController {
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
     }
 
-    public void setRotationLockedAtAngle(int rotationSuggestion) {
+    public void setRotationLockedAtAngle(int rotationSuggestion, String caller) {
         RotationPolicy.setRotationLockAtAngle(mContext, /* enabled= */ isRotationLocked(),
-                /* rotation= */ rotationSuggestion);
+                /* rotation= */ rotationSuggestion, caller);
     }
 
     public boolean isRotationLocked() {
@@ -468,7 +468,8 @@ public class RotationButtonController {
         if (rotationLocked || mRotationButton.isVisible()) {
             // Do not allow a change in rotation to set user rotation when docked.
             if (shouldOverrideUserLockPrefs(rotation) && rotationLocked && !mDocked) {
-                setRotationLockedAtAngle(rotation);
+                setRotationLockedAtAngle(rotation, /* caller= */
+                        "RotationButtonController#onRotationWatcherChanged");
             }
             setRotateSuggestionButtonState(false /* visible */, true /* forced */);
         }
@@ -572,7 +573,8 @@ public class RotationButtonController {
     private void onRotateSuggestionClick(View v) {
         mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED);
         incrementNumAcceptedRotationSuggestionsIfNeeded();
-        setRotationLockedAtAngle(mLastRotationSuggestion);
+        setRotationLockedAtAngle(mLastRotationSuggestion,
+                /* caller= */ "RotationButtonController#onRotateSuggestionClick");
         Log.i(TAG, "onRotateSuggestionClick() mLastRotationSuggestion=" + mLastRotationSuggestion);
         v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 57a42247af76cc1c27d8cfde1e14da28f28a0cf4..4cfc6aaa2b50233cc9a27dc3dd1544fb4ae14aef 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -185,9 +185,6 @@ constructor(
     /** Whether the pattern should be visible for the currently-selected user. */
     val isPatternVisible: StateFlow<Boolean> = repository.isPatternVisible
 
-    /** The minimal length of a pattern. */
-    val minPatternLength: Int = repository.minPatternLength
-
     private var throttlingCountdownJob: Job? = null
 
     init {
@@ -243,39 +240,46 @@ constructor(
      * Attempts to authenticate the user and unlock the device.
      *
      * If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method
-     * supports auto-confirming, and the input's length is at least the code's length. Otherwise,
-     * `null` is returned.
+     * supports auto-confirming, and the input's length is at least the required length. Otherwise,
+     * `AuthenticationResult.SKIPPED` is returned.
      *
      * @param input The input from the user to try to authenticate with. This can be a list of
      *   different things, based on the current authentication method.
      * @param tryAutoConfirm `true` if called while the user inputs the code, without an explicit
      *   request to validate.
-     * @return `true` if the authentication succeeded and the device is now unlocked; `false` when
-     *   authentication failed, `null` if the check was not performed.
+     * @return The result of this authentication attempt.
      */
-    suspend fun authenticate(input: List<Any>, tryAutoConfirm: Boolean = false): Boolean? {
+    suspend fun authenticate(
+        input: List<Any>,
+        tryAutoConfirm: Boolean = false
+    ): AuthenticationResult {
         if (input.isEmpty()) {
             throw IllegalArgumentException("Input was empty!")
         }
 
+        val authMethod = getAuthenticationMethod()
         val skipCheck =
             when {
                 // We're being throttled, the UI layer should not have called this; skip the
                 // attempt.
                 isThrottled.value -> true
+                // The pattern is too short; skip the attempt.
+                authMethod == DomainLayerAuthenticationMethodModel.Pattern &&
+                    input.size < repository.minPatternLength -> true
                 // Auto-confirm attempt when the feature is not enabled; skip the attempt.
                 tryAutoConfirm && !isAutoConfirmEnabled.value -> true
                 // Auto-confirm should skip the attempt if the pin entered is too short.
-                tryAutoConfirm && input.size < repository.getPinLength() -> true
+                tryAutoConfirm &&
+                    authMethod == DomainLayerAuthenticationMethodModel.Pin &&
+                    input.size < repository.getPinLength() -> true
                 else -> false
             }
         if (skipCheck) {
-            return null
+            return AuthenticationResult.SKIPPED
         }
 
         // Attempt to authenticate:
-        val authMethod = getAuthenticationMethod()
-        val credential = authMethod.createCredential(input) ?: return null
+        val credential = authMethod.createCredential(input) ?: return AuthenticationResult.SKIPPED
         val authenticationResult = repository.checkCredential(credential)
         credential.zeroize()
 
@@ -299,7 +303,11 @@ constructor(
             refreshThrottling()
         }
 
-        return authenticationResult.isSuccessful
+        return if (authenticationResult.isSuccessful) {
+            AuthenticationResult.SUCCEEDED
+        } else {
+            AuthenticationResult.FAILED
+        }
     }
 
     /** Starts refreshing the throttling state every second. */
@@ -383,3 +391,16 @@ constructor(
         }
     }
 }
+
+/** Result of a user authentication attempt. */
+enum class AuthenticationResult {
+    /** Authentication succeeded and the device is now unlocked. */
+    SUCCEEDED,
+    /** Authentication failed and the device remains unlocked. */
+    FAILED,
+    /**
+     * Authentication was not performed, e.g. due to insufficient input, and the device remains
+     * unlocked.
+     */
+    SKIPPED,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 9b2f2baba94bde8fcec02e1c3229654331894a76..f3a463ba44a449961b48d9ebaec5a0e91b83e5f9 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.bouncer.domain.interactor
 
 import android.content.Context
-import com.android.systemui.res.R
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.domain.interactor.AuthenticationResult
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.bouncer.data.repository.BouncerRepository
@@ -26,6 +26,7 @@ import com.android.systemui.classifier.FalsingClassifier
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
@@ -34,6 +35,7 @@ import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -92,9 +94,6 @@ constructor(
     /** Whether the pattern should be visible for the currently-selected user. */
     val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible
 
-    /** The minimal length of a pattern. */
-    val minPatternLength = authenticationInteractor.minPatternLength
-
     init {
         if (flags.isEnabled()) {
             // Clear the message if moved from throttling to no-longer throttling.
@@ -184,33 +183,44 @@ constructor(
      * dismissed and hidden.
      *
      * If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method
-     * supports auto-confirming, and the input's length is at least the code's length. Otherwise,
-     * `null` is returned.
+     * supports auto-confirming, and the input's length is at least the required length. Otherwise,
+     * `AuthenticationResult.SKIPPED` is returned.
      *
      * @param input The input from the user to try to authenticate with. This can be a list of
      *   different things, based on the current authentication method.
      * @param tryAutoConfirm `true` if called while the user inputs the code, without an explicit
      *   request to validate.
-     * @return `true` if the authentication succeeded and the device is now unlocked; `false` when
-     *   authentication failed, `null` if the check was not performed.
+     * @return The result of this authentication attempt.
      */
     suspend fun authenticate(
         input: List<Any>,
         tryAutoConfirm: Boolean = false,
-    ): Boolean? {
-        val isAuthenticated =
-            authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null
-
-        if (isAuthenticated) {
-            sceneInteractor.changeScene(
-                scene = SceneModel(SceneKey.Gone),
-                loggingReason = "successful authentication",
-            )
-        } else {
-            showErrorMessage()
+    ): AuthenticationResult {
+        if (input.isEmpty()) {
+            return AuthenticationResult.SKIPPED
         }
-
-        return isAuthenticated
+        // Switching to the application scope here since this method is often called from
+        // view-models, whose lifecycle (and thus scope) is shorter than this interactor.
+        // This allows the task to continue running properly even when the calling scope has been
+        // cancelled.
+        return applicationScope
+            .async {
+                val authResult = authenticationInteractor.authenticate(input, tryAutoConfirm)
+                when (authResult) {
+                    // Authentication succeeded.
+                    AuthenticationResult.SUCCEEDED ->
+                        sceneInteractor.changeScene(
+                            scene = SceneModel(SceneKey.Gone),
+                            loggingReason = "successful authentication",
+                        )
+                    // Authentication failed.
+                    AuthenticationResult.FAILED -> showErrorMessage()
+                    // Authentication skipped.
+                    AuthenticationResult.SKIPPED -> if (!tryAutoConfirm) showErrorMessage()
+                }
+                authResult
+            }
+            .await()
     }
 
     /**
@@ -221,21 +231,20 @@ constructor(
      * For example, if the user entered a pattern that's too short, the system can show the error
      * message without having the attempt trigger throttling.
      */
-    suspend fun showErrorMessage() {
+    private suspend fun showErrorMessage() {
         repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod()))
     }
 
-    /** If the bouncer is showing, hides the bouncer and return to the lockscreen scene. */
-    fun hide(
-        loggingReason: String,
-    ) {
+    /** Notifies the interactor that the input method editor has been hidden. */
+    fun onImeHidden() {
+        // If the bouncer is showing, hide it and return to the lockscreen scene.
         if (sceneInteractor.desiredScene.value.key != SceneKey.Bouncer) {
             return
         }
 
         sceneInteractor.changeScene(
             scene = SceneModel(SceneKey.Lockscreen),
-            loggingReason = loggingReason,
+            loggingReason = "IME hidden",
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index 4546bea3b89b09271e9c2217b4a27ca810bf537a..66c6162533bf67ae6342b94b18b90e2ac7190649 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -16,12 +16,21 @@
 
 package com.android.systemui.bouncer.ui.viewmodel
 
+import android.annotation.StringRes
+import android.util.Log
+import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
 
 sealed class AuthMethodBouncerViewModel(
+    protected val viewModelScope: CoroutineScope,
+    protected val interactor: BouncerInteractor,
+
     /**
      * Whether user input is enabled.
      *
@@ -29,7 +38,6 @@ sealed class AuthMethodBouncerViewModel(
      * being able to attempt to unlock the device.
      */
     val isInputEnabled: StateFlow<Boolean>,
-    private val interactor: BouncerInteractor,
 ) {
 
     private val _animateFailure = MutableStateFlow(false)
@@ -42,12 +50,26 @@ sealed class AuthMethodBouncerViewModel(
     /** Whether the input method editor (for example, the software keyboard) is visible. */
     private var isImeVisible: Boolean = false
 
+    /** The authentication method that corresponds to this view model. */
+    abstract val authenticationMethod: AuthenticationMethodModel
+
     /**
-     * Notifies that the failure animation has been shown. This should be called to consume a `true`
-     * value in [animateFailure].
+     * String resource ID of the failure message to be shown during throttling.
+     *
+     * The message must include 2 number parameters: the first one indicating how many unsuccessful
+     * attempts were made, and the second one indicating in how many seconds throttling will expire.
      */
-    fun onFailureAnimationShown() {
-        _animateFailure.value = false
+    @get:StringRes abstract val throttlingMessageId: Int
+
+    /** Notifies that the UI has been shown to the user. */
+    fun onShown() {
+        clearInput()
+        interactor.resetMessage()
+    }
+
+    /** Notifies that the user has placed down a pointer. */
+    fun onDown() {
+        interactor.onDown()
     }
 
     /**
@@ -56,17 +78,44 @@ sealed class AuthMethodBouncerViewModel(
      */
     fun onImeVisibilityChanged(isVisible: Boolean) {
         if (isImeVisible && !isVisible) {
-            // The IME has gone from visible to invisible, dismiss the bouncer.
-            interactor.hide(
-                loggingReason = "IME hidden",
-            )
+            interactor.onImeHidden()
         }
 
         isImeVisible = isVisible
     }
 
-    /** Ask the UI to show the failure animation. */
-    protected fun showFailureAnimation() {
-        _animateFailure.value = true
+    /**
+     * Notifies that the failure animation has been shown. This should be called to consume a `true`
+     * value in [animateFailure].
+     */
+    fun onFailureAnimationShown() {
+        _animateFailure.value = false
+    }
+
+    /** Clears any previously-entered input. */
+    protected abstract fun clearInput()
+
+    /** Returns the input entered so far. */
+    protected abstract fun getInput(): List<Any>
+
+    /**
+     * Attempts to authenticate the user using the current input value.
+     *
+     * @see BouncerInteractor.authenticate
+     */
+    protected fun tryAuthenticate(useAutoConfirm: Boolean = false) {
+        viewModelScope.launch {
+            Log.d("Danny", "tryAuthenticate(useAutoConfirm=$useAutoConfirm)")
+            val authenticationResult = interactor.authenticate(getInput(), useAutoConfirm)
+            Log.d("Danny", "result = $authenticationResult")
+            if (authenticationResult == AuthenticationResult.SKIPPED && useAutoConfirm) {
+                return@launch
+            }
+            _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
+
+            // TODO(b/291528545): On success, this should only be cleared after the view is animated
+            //  away).
+            clearInput()
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 15d1dae239a06788d38c028b140c6f817593f648..782ead360d5117f94f3eb28ecd5b6e5baf78f872 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -17,16 +17,19 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import android.content.Context
-import com.android.systemui.res.R
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import javax.inject.Inject
 import kotlin.math.ceil
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -35,6 +38,7 @@ import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.job
 import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input on bouncer UIs. */
@@ -44,8 +48,9 @@ class BouncerViewModel
 constructor(
     @Application private val applicationContext: Context,
     @Application private val applicationScope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
     private val bouncerInteractor: BouncerInteractor,
-    private val authenticationInteractor: AuthenticationInteractor,
+    authenticationInteractor: AuthenticationInteractor,
     flags: SceneContainerFlags,
 ) {
     private val isInputEnabled: StateFlow<Boolean> =
@@ -57,91 +62,45 @@ constructor(
                 initialValue = !bouncerInteractor.isThrottled.value,
             )
 
-    private val pin: PinBouncerViewModel by lazy {
-        PinBouncerViewModel(
-            applicationContext = applicationContext,
-            applicationScope = applicationScope,
-            interactor = bouncerInteractor,
-            isInputEnabled = isInputEnabled,
-        )
-    }
-
-    private val password: PasswordBouncerViewModel by lazy {
-        PasswordBouncerViewModel(
-            applicationScope = applicationScope,
-            interactor = bouncerInteractor,
-            isInputEnabled = isInputEnabled,
-        )
-    }
-
-    private val pattern: PatternBouncerViewModel by lazy {
-        PatternBouncerViewModel(
-            applicationContext = applicationContext,
-            applicationScope = applicationScope,
-            interactor = bouncerInteractor,
-            isInputEnabled = isInputEnabled,
-        )
-    }
-
     /** View-model for the current UI, based on the current authentication method. */
-    val authMethod: StateFlow<AuthMethodBouncerViewModel?> =
+    val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> =
         authenticationInteractor.authenticationMethod
-            .map { authenticationMethod ->
-                when (authenticationMethod) {
-                    is AuthenticationMethodModel.Pin -> pin
-                    is AuthenticationMethodModel.Password -> password
-                    is AuthenticationMethodModel.Pattern -> pattern
-                    else -> null
-                }
-            }
+            .map(::getChildViewModel)
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue = null,
             )
 
+    // Handle to the scope of the child ViewModel (stored in [authMethod]).
+    private var childViewModelScope: CoroutineScope? = null
+
     init {
         if (flags.isEnabled()) {
             applicationScope.launch {
-                bouncerInteractor.isThrottled
-                    .map { isThrottled ->
-                        if (isThrottled) {
-                            when (authenticationInteractor.getAuthenticationMethod()) {
-                                is AuthenticationMethodModel.Pin ->
-                                    R.string.kg_too_many_failed_pin_attempts_dialog_message
-                                is AuthenticationMethodModel.Password ->
-                                    R.string.kg_too_many_failed_password_attempts_dialog_message
-                                is AuthenticationMethodModel.Pattern ->
-                                    R.string.kg_too_many_failed_pattern_attempts_dialog_message
-                                else -> null
-                            }?.let { stringResourceId ->
-                                applicationContext.getString(
-                                    stringResourceId,
-                                    bouncerInteractor.throttling.value.failedAttemptCount,
-                                    ceil(bouncerInteractor.throttling.value.remainingMs / 1000f)
-                                        .toInt(),
-                                )
-                            }
+                combine(bouncerInteractor.isThrottled, authMethodViewModel) {
+                        isThrottled,
+                        authMethodViewModel ->
+                        if (isThrottled && authMethodViewModel != null) {
+                            applicationContext.getString(
+                                authMethodViewModel.throttlingMessageId,
+                                bouncerInteractor.throttling.value.failedAttemptCount,
+                                ceil(bouncerInteractor.throttling.value.remainingMs / 1000f)
+                                    .toInt(),
+                            )
                         } else {
                             null
                         }
                     }
                     .distinctUntilChanged()
-                    .collect { dialogMessageOrNull ->
-                        if (dialogMessageOrNull != null) {
-                            _throttlingDialogMessage.value = dialogMessageOrNull
-                        }
-                    }
+                    .collect { dialogMessage -> _throttlingDialogMessage.value = dialogMessage }
             }
         }
     }
 
     /** The user-facing message to show in the bouncer. */
     val message: StateFlow<MessageViewModel> =
-        combine(
-                bouncerInteractor.message,
-                bouncerInteractor.isThrottled,
-            ) { message, isThrottled ->
+        combine(bouncerInteractor.message, bouncerInteractor.isThrottled) { message, isThrottled ->
                 toMessageViewModel(message, isThrottled)
             }
             .stateIn(
@@ -186,6 +145,50 @@ constructor(
         )
     }
 
+    private fun getChildViewModel(
+        authenticationMethod: AuthenticationMethodModel,
+    ): AuthMethodBouncerViewModel? {
+        // If the current child view-model matches the authentication method, reuse it instead of
+        // creating a new instance.
+        val childViewModel = authMethodViewModel.value
+        if (authenticationMethod == childViewModel?.authenticationMethod) {
+            return childViewModel
+        }
+
+        childViewModelScope?.cancel()
+        val newViewModelScope = createChildCoroutineScope(applicationScope)
+        childViewModelScope = newViewModelScope
+        return when (authenticationMethod) {
+            is AuthenticationMethodModel.Pin ->
+                PinBouncerViewModel(
+                    applicationContext = applicationContext,
+                    viewModelScope = newViewModelScope,
+                    interactor = bouncerInteractor,
+                    isInputEnabled = isInputEnabled,
+                )
+            is AuthenticationMethodModel.Password ->
+                PasswordBouncerViewModel(
+                    viewModelScope = newViewModelScope,
+                    interactor = bouncerInteractor,
+                    isInputEnabled = isInputEnabled,
+                )
+            is AuthenticationMethodModel.Pattern ->
+                PatternBouncerViewModel(
+                    applicationContext = applicationContext,
+                    viewModelScope = newViewModelScope,
+                    interactor = bouncerInteractor,
+                    isInputEnabled = isInputEnabled,
+                )
+            else -> null
+        }
+    }
+
+    private fun createChildCoroutineScope(parentScope: CoroutineScope): CoroutineScope {
+        return CoroutineScope(
+            SupervisorJob(parent = parentScope.coroutineContext.job) + mainDispatcher
+        )
+    }
+
     data class MessageViewModel(
         val text: String,
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 9e10f29a00f94c54c87bcbe38c8db160452ae938..fe7741930d3317ae79c3c0309c861ee8f5e451cd 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -16,22 +16,24 @@
 
 package com.android.systemui.bouncer.ui.viewmodel
 
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.res.R
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the password bouncer UI. */
 class PasswordBouncerViewModel(
-    private val applicationScope: CoroutineScope,
-    private val interactor: BouncerInteractor,
+    viewModelScope: CoroutineScope,
+    interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
 ) :
     AuthMethodBouncerViewModel(
-        isInputEnabled = isInputEnabled,
+        viewModelScope = viewModelScope,
         interactor = interactor,
+        isInputEnabled = isInputEnabled,
     ) {
 
     private val _password = MutableStateFlow("")
@@ -39,10 +41,16 @@ class PasswordBouncerViewModel(
     /** The password entered so far. */
     val password: StateFlow<String> = _password.asStateFlow()
 
-    /** Notifies that the UI has been shown to the user. */
-    fun onShown() {
+    override val authenticationMethod = AuthenticationMethodModel.Password
+
+    override val throttlingMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
+
+    override fun clearInput() {
         _password.value = ""
-        interactor.resetMessage()
+    }
+
+    override fun getInput(): List<Any> {
+        return _password.value.toCharArray().toList()
     }
 
     /** Notifies that the user has changed the password input. */
@@ -60,17 +68,8 @@ class PasswordBouncerViewModel(
 
     /** Notifies that the user has pressed the key for attempting to authenticate the password. */
     fun onAuthenticateKeyPressed() {
-        val password = _password.value.toCharArray().toList()
-        if (password.isEmpty()) {
-            return
-        }
-
-        _password.value = ""
-
-        applicationScope.launch {
-            if (interactor.authenticate(password) != true) {
-                showFailureAnimation()
-            }
+        if (_password.value.isNotEmpty()) {
+            tryAuthenticate()
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 497276b479960d738e1b155e2e9f65dbd9164fa2..52adf54f8d24f1b33836619ac739f09d85f8d9a8 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -18,8 +18,10 @@ package com.android.systemui.bouncer.ui.viewmodel
 
 import android.content.Context
 import android.util.TypedValue
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.res.R
 import kotlin.math.max
 import kotlin.math.min
 import kotlin.math.pow
@@ -31,18 +33,18 @@ import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the pattern bouncer UI. */
 class PatternBouncerViewModel(
     private val applicationContext: Context,
-    private val applicationScope: CoroutineScope,
-    private val interactor: BouncerInteractor,
+    viewModelScope: CoroutineScope,
+    interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
 ) :
     AuthMethodBouncerViewModel(
-        isInputEnabled = isInputEnabled,
+        viewModelScope = viewModelScope,
         interactor = interactor,
+        isInputEnabled = isInputEnabled,
     ) {
 
     /** The number of columns in the dot grid. */
@@ -58,7 +60,7 @@ class PatternBouncerViewModel(
         _selectedDots
             .map { it.toList() }
             .stateIn(
-                scope = applicationScope,
+                scope = viewModelScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue = emptyList(),
             )
@@ -76,15 +78,9 @@ class PatternBouncerViewModel(
     /** Whether the pattern itself should be rendered visibly. */
     val isPatternVisible: StateFlow<Boolean> = interactor.isPatternVisible
 
-    /** Notifies that the UI has been shown to the user. */
-    fun onShown() {
-        interactor.resetMessage()
-    }
+    override val authenticationMethod = AuthenticationMethodModel.Pattern
 
-    /** Notifies that the user has placed down a pointer, not necessarily dragging just yet. */
-    fun onDown() {
-        interactor.onDown()
-    }
+    override val throttlingMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message
 
     /** Notifies that the user has started a drag gesture across the dot grid. */
     fun onDragStart() {
@@ -164,24 +160,23 @@ class PatternBouncerViewModel(
 
     /** Notifies that the user has ended the drag gesture across the dot grid. */
     fun onDragEnd() {
-        val pattern = _selectedDots.value.map { it.toCoordinate() }
-
+        val pattern = getInput()
         if (pattern.size == 1) {
             // Single dot patterns are treated as erroneous/false taps:
             interactor.onFalseUserInput()
         }
 
+        tryAuthenticate()
+    }
+
+    override fun clearInput() {
         _dots.value = defaultDots()
         _currentDot.value = null
         _selectedDots.value = linkedSetOf()
+    }
 
-        applicationScope.launch {
-            if (pattern.size < interactor.minPatternLength) {
-                interactor.showErrorMessage()
-            } else if (interactor.authenticate(pattern) != true) {
-                showFailureAnimation()
-            }
-        }
+    override fun getInput(): List<Any> {
+        return _selectedDots.value.map(PatternDotViewModel::toCoordinate)
     }
 
     private fun defaultDots(): List<PatternDotViewModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 8e6421ed3f0ab1bc348d7b2917dc2aa26aa5ce60..b90e255267261c652f404a4b7043ac0e44e35d91 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -18,7 +18,9 @@ package com.android.systemui.bouncer.ui.viewmodel
 
 import android.content.Context
 import com.android.keyguard.PinShapeAdapter
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.res.R
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
@@ -26,18 +28,18 @@ import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the PIN code bouncer UI. */
 class PinBouncerViewModel(
     applicationContext: Context,
-    private val applicationScope: CoroutineScope,
-    private val interactor: BouncerInteractor,
+    viewModelScope: CoroutineScope,
+    interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
 ) :
     AuthMethodBouncerViewModel(
-        isInputEnabled = isInputEnabled,
+        viewModelScope = viewModelScope,
         interactor = interactor,
+        isInputEnabled = isInputEnabled,
     ) {
 
     val pinShapes = PinShapeAdapter(applicationContext)
@@ -61,7 +63,7 @@ class PinBouncerViewModel(
                 )
             }
             .stateIn(
-                scope = applicationScope,
+                scope = viewModelScope,
                 // Make sure this is kept as WhileSubscribed or we can run into a bug where the
                 // downstream continues to receive old/stale/cached values.
                 started = SharingStarted.WhileSubscribed(),
@@ -73,21 +75,14 @@ class PinBouncerViewModel(
         interactor.isAutoConfirmEnabled
             .map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown }
             .stateIn(
-                scope = applicationScope,
+                scope = viewModelScope,
                 started = SharingStarted.Eagerly,
                 initialValue = ActionButtonAppearance.Hidden,
             )
 
-    /** Notifies that the UI has been shown to the user. */
-    fun onShown() {
-        clearPinInput()
-        interactor.resetMessage()
-    }
+    override val authenticationMethod = AuthenticationMethodModel.Pin
 
-    /** Notifies that the user has placed down a pointer. */
-    fun onDown() {
-        interactor.onDown()
-    }
+    override val throttlingMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message
 
     /** Notifies that the user clicked on a PIN button with the given digit value. */
     fun onPinButtonClicked(input: Int) {
@@ -109,7 +104,8 @@ class PinBouncerViewModel(
 
     /** Notifies that the user long-pressed the backspace button. */
     fun onBackspaceButtonLongPressed() {
-        clearPinInput()
+        clearInput()
+        interactor.clearMessage()
     }
 
     /** Notifies that the user clicked the "enter" button. */
@@ -117,24 +113,12 @@ class PinBouncerViewModel(
         tryAuthenticate(useAutoConfirm = false)
     }
 
-    private fun clearPinInput() {
+    override fun clearInput() {
         mutablePinInput.value = mutablePinInput.value.clearAll()
     }
 
-    private fun tryAuthenticate(useAutoConfirm: Boolean) {
-        val pinCode = mutablePinInput.value.getPin()
-
-        applicationScope.launch {
-            val isSuccess = interactor.authenticate(pinCode, useAutoConfirm) ?: return@launch
-
-            if (!isSuccess) {
-                showFailureAnimation()
-            }
-
-            // TODO(b/291528545): this should not be cleared on success (at least until the view
-            // is animated away).
-            clearPinInput()
-        }
+    override fun getInput(): List<Any> {
+        return mutablePinInput.value.getPin()
     }
 
     private fun computeBackspaceButtonAppearance(
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ecad9d7c67bcbe1642d403cc25f4d73f1c68f56d..3b70555ad32a68757eed724f9070195d741abe6c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -253,7 +253,8 @@ object Flags {
 
     /** Provide new auth messages on the bouncer. */
     // TODO(b/277961132): Tracking bug.
-    @JvmField val REVAMPED_BOUNCER_MESSAGES = unreleasedFlag("revamped_bouncer_messages")
+    @JvmField val REVAMPED_BOUNCER_MESSAGES = unreleasedFlag("revamped_bouncer_messages",
+            teamfood = true)
 
     /** Keyguard Migration */
 
@@ -297,6 +298,11 @@ object Flags {
     @JvmField val MIGRATE_KEYGUARD_STATUS_BAR_VIEW =
         unreleasedFlag("migrate_keyguard_status_bar_view")
 
+    /** Migrate clocks from keyguard status view to keyguard root view*/
+    // TODO(b/301502635): Tracking Bug.
+    @JvmField val MIGRATE_CLOCKS_TO_BLUEPRINT =
+            unreleasedFlag("migrate_clocks_to_blueprint")
+
     /** Enables preview loading animation in the wallpaper picker. */
     // TODO(b/274443705): Tracking Bug
     @JvmField
@@ -762,6 +768,9 @@ object Flags {
     // TODO(b/289573946): Tracking Bug
     @JvmField val PRECOMPUTED_TEXT = unreleasedFlag("precomputed_text", teamfood = true)
 
+    // TODO(b/302087895): Tracking Bug
+    @JvmField val CALL_LAYOUT_ASYNC_SET_DATA = unreleasedFlag("call_layout_async_set_data")
+
     // 2900 - CentralSurfaces-related flags
 
     // TODO(b/285174336): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index e59fc15c3876866b42daf9759cf44183fa5504d6..a48e56a9f158eec982749c58e8fa3bb20ea142dd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -606,6 +606,9 @@ constructor(
                 }
                     ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex)
             }
+        } else if (isRtl && mediaContent.childCount > 0) {
+            // In RTL, Scroll to the first player as it is the rightmost player in media carousel.
+            mediaCarouselScrollHandler.scrollToPlayer(destIndex = 0)
         }
         // Check postcondition: mediaContent should have the same number of children as there
         // are
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index 126115294e75ab8ebc6b2324a63681cbab994dec..02f0d12c7069742204a44c6da4694fec4a62554a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -282,13 +282,14 @@ class MediaCarouselScrollHandler(
             // It's an up and the fling didn't take it above
             val relativePos = scrollView.relativeScrollX % playerWidthPlusPadding
             val scrollXAmount: Int =
-                if (isRtl xor (relativePos > playerWidthPlusPadding / 2)) {
+                if (relativePos > playerWidthPlusPadding / 2) {
                     playerWidthPlusPadding - relativePos
                 } else {
                     -1 * relativePos
                 }
             if (scrollXAmount != 0) {
-                val newScrollX = scrollView.relativeScrollX + scrollXAmount
+                val dx = if (isRtl) -scrollXAmount else scrollXAmount
+                val newScrollX = scrollView.scrollX + dx
                 // Delay the scrolling since scrollView calls springback which cancels
                 // the animation again..
                 mainExecutor.execute { scrollView.smoothScrollTo(newScrollX, scrollView.scrollY) }
@@ -539,7 +540,8 @@ class MediaCarouselScrollHandler(
         // If the removed media item is "left of" the active one (in an absolute sense), we need to
         // scroll the view to keep that player in view.  This is because scroll position is always
         // calculated from left to right.
-        val leftOfActive = if (isRtl) !beforeActive else beforeActive
+        // For RTL, we need to scroll if the visible media player is the last item.
+        val leftOfActive = if (isRtl && visibleMediaIndex != 0) !beforeActive else beforeActive
         if (leftOfActive) {
             scrollView.scrollX = Math.max(scrollView.scrollX - playerWidthPlusPadding, 0)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt
index 0e0746590776b26220e76582370b47d05a96bf12..10512f1c4aed709fafca91ffa8b49cb8aa8cf570 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt
@@ -74,6 +74,14 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
             scrollX = transformScrollX(value)
         }
 
+    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+        if (!isLaidOut && isLayoutRtl) {
+            // Reset scroll because onLayout method overrides RTL scroll if view was not laid out.
+            mScrollX = relativeScrollX
+        }
+        super.onLayout(changed, l, t, r, b)
+    }
+
     /** Allow all scrolls to go through, use base implementation */
     override fun scrollTo(x: Int, y: Int) {
         if (mScrollX != x || mScrollY != y) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index cb52a5fb53fa759740f27d583477f64123494b2a..ad1c77d055348a3b067fa6e03d0dd6d150545e2c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -795,7 +795,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
             // Reset user rotation pref to match that of the WindowManager if starting in locked
             // mode. This will automatically happen when switching from auto-rotate to locked mode.
             if (display != null && rotationButtonController.isRotationLocked()) {
-                rotationButtonController.setRotationLockedAtAngle(display.getRotation());
+                rotationButtonController.setRotationLockedAtAngle(
+                        display.getRotation(), /* caller= */ "NavigationBar#onViewAttached");
             }
         } else {
             mDisabledFlags2 |= StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 826f75f2f63b6b26247e2f20c0371f8135e2f9f1..19012e29b184d86916eddd15139750257fb19dfc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -78,6 +78,14 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
     private int mMinRows = 1;
     private int mMaxColumns = TileLayout.NO_MAX_COLUMNS;
 
+    /**
+     * it's fine to read this value when class is initialized because SysUI is always restarted
+     * when running tests in test harness, see SysUiTestIsolationRule. This check is done quite
+     * often - with every shade open action - so we don't want to potentially make it less
+     * performant only for test use case
+     */
+    private boolean mRunningInTestHarness = ActivityManager.isRunningInTestHarness();
+
     public PagedTileLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
         mScroller = new Scroller(context, SCROLL_CUBIC);
@@ -590,11 +598,11 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
     private boolean shouldNotRunAnimation(Set<String> tilesToReveal) {
         boolean noAnimationNeeded = tilesToReveal.isEmpty() || mPages.size() < 2;
         boolean scrollingInProgress = getScrollX() != 0 || !beginFakeDrag();
-        // isRunningInTestHarness() to disable animation in functional testing as it caused
+        // checking mRunningInTestHarness to disable animation in functional testing as it caused
         // flakiness and is not needed there. Alternative solutions were more complex and would
         // still be either potentially flaky or modify internal data.
         // For more info see b/253493927 and b/293234595
-        return noAnimationNeeded || scrollingInProgress || ActivityManager.isRunningInTestHarness();
+        return noAnimationNeeded || scrollingInProgress || mRunningInTestHarness;
     }
 
     private int sanitizePageAction(int action) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index ea162fa2921fb2ae44674ee0ffbebb5b51ee3167..5a9500494de06108d433a263163e10e24cd85bee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -133,7 +133,7 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements
     @Override
     protected void handleClick(@Nullable View view) {
         final boolean newState = !mState.value;
-        mController.setRotationLocked(!newState);
+        mController.setRotationLocked(!newState, /* caller= */ "RotationLockTile#handleClick");
         refreshState(newState);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index e632214bcb2b0474194ed3939e52e86473bc8b15..37a4ef168423bf17417edb493e66ccadd541803c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -104,8 +104,9 @@ public class StatusBarStateControllerImpl implements
     // Record the HISTORY_SIZE most recent states
     private int mHistoryIndex = 0;
     private HistoricalState[] mHistoricalRecords = new HistoricalState[HISTORY_SIZE];
-    // This is used by InteractionJankMonitor to get callback from HWUI.
+    // These views are used by InteractionJankMonitor to get callback from HWUI.
     private View mView;
+    private KeyguardClockSwitch mClockSwitchView;
 
     /**
      * If any of the system bars is hidden.
@@ -334,6 +335,7 @@ public class StatusBarStateControllerImpl implements
         if ((mView == null || !mView.isAttachedToWindow())
                 && (view != null && view.isAttachedToWindow())) {
             mView = view;
+            mClockSwitchView = view.findViewById(R.id.keyguard_clock_container);
         }
         mDozeAmountTarget = dozeAmount;
         if (animated) {
@@ -416,21 +418,12 @@ public class StatusBarStateControllerImpl implements
 
     /** Returns the id of the currently rendering clock */
     public String getClockId() {
-        if (mView == null) {
-            return KeyguardClockSwitch.MISSING_CLOCK_ID;
-        }
-
-        View clockSwitch = mView.findViewById(R.id.keyguard_clock_container);
-        if (clockSwitch == null) {
+        if (mClockSwitchView == null) {
             Log.e(TAG, "Clock container was missing");
             return KeyguardClockSwitch.MISSING_CLOCK_ID;
         }
-        if (!(clockSwitch instanceof KeyguardClockSwitch)) {
-            Log.e(TAG, "Clock container was incorrect type: " + clockSwitch);
-            return KeyguardClockSwitch.MISSING_CLOCK_ID;
-        }
 
-        return ((KeyguardClockSwitch) clockSwitch).getClockId();
+        return mClockSwitchView.getClockId();
     }
 
     private void beginInteractionJankMonitor() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index eb31bd3a95fe1018d9674b17115620fbcb137e27..2d5afd56da720c4c4a2845bc016ce2393f0bd63b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -50,7 +50,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
      * Set of summary keys whose groups are expanded.
      * NOTE: This should not be modified without notifying listeners, so prefer using
      * {@code setGroupExpanded} when making changes.
-      */
+     */
     private final Set<NotificationEntry> mExpandedGroups = new HashSet<>();
 
     private final FeatureFlags mFeatureFlags;
@@ -104,7 +104,18 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
 
     @Override
     public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
-        final NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry);
+        NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry);
+        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)
+                && entry.getParent() == null) {
+            if (expanded) {
+                throw new IllegalArgumentException("Cannot expand group that is not attached");
+            } else {
+                // The entry is no longer attached, but we still want to make sure we don't have
+                // a stale expansion state.
+                groupSummary = entry;
+            }
+        }
+
         boolean changed;
         if (expanded) {
             changed = mExpandedGroups.add(groupSummary);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java
index c33e8ab8cdd49a94304deab531d0f453f01f774d..3158782e6fea28284b38c79ca545fa4d3a206efb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java
@@ -25,18 +25,18 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import java.util.List;
 
 /**
- * Helper that determines the group states (parent, summary, children) of a notification.
+ * Helper that determines the group states (parent, summary, children) of a notification. This
+ * generally assumes that the notification is attached (aka its parent is not null).
  */
 public interface GroupMembershipManager {
     /**
-     * @return whether a given notification is a top level entry or is the summary in a group which
-     * has children
+     * @return whether a given notification is the summary in a group which has children
      */
     boolean isGroupSummary(@NonNull NotificationEntry entry);
 
     /**
      * Get the summary of a specified status bar notification. For an isolated notification this
-     * returns itself.
+     * returns null, but if called directly on a summary it returns itself.
      */
     @Nullable
     NotificationEntry getGroupSummary(@NonNull NotificationEntry entry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
index a6b855f9b838e30c368e8f022911266230a59513..cb7935369564eca617cf3c07cb16850b2a7fccb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
@@ -22,7 +22,7 @@ import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
@@ -38,47 +38,50 @@ import javax.inject.Inject;
  */
 @SysUISingleton
 public class GroupMembershipManagerImpl implements GroupMembershipManager {
-    FeatureFlags mFeatureFlags;
+    FeatureFlagsClassic mFeatureFlags;
 
     @Inject
-    public GroupMembershipManagerImpl(FeatureFlags featureFlags) {
+    public GroupMembershipManagerImpl(FeatureFlagsClassic featureFlags) {
         mFeatureFlags = featureFlags;
     }
 
     @Override
     public boolean isGroupSummary(@NonNull NotificationEntry entry) {
-        return getGroupSummary(entry) == entry;
+        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
+            if (entry.getParent() == null) {
+                // The entry is not attached, so it doesn't count.
+                return false;
+            }
+            // If entry is a summary, its parent is a GroupEntry with summary = entry.
+            return entry.getParent().getSummary() == entry;
+        } else {
+            return getGroupSummary(entry) == entry;
+        }
     }
 
     @Nullable
     @Override
     public NotificationEntry getGroupSummary(@NonNull NotificationEntry entry) {
-        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
-            if (!isChildInGroup(entry)) {
-                return entry.getRepresentativeEntry();
-            }
-        } else {
-            if (isEntryTopLevel(entry) || entry.getParent() == null) {
-                return null;
-            }
+        if (isTopLevelEntry(entry) || entry.getParent() == null) {
+            return null;
         }
-
-        return entry.getParent().getRepresentativeEntry();
+        return entry.getParent().getSummary();
     }
 
     @Override
     public boolean isChildInGroup(@NonNull NotificationEntry entry) {
         if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
-            return !isEntryTopLevel(entry) && entry.getParent() != null;
+            // An entry is a child if it's not a summary or top level entry, but it is attached.
+            return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null;
         } else {
-            return !isEntryTopLevel(entry);
+            return !isTopLevelEntry(entry);
         }
     }
 
     @Override
     public boolean isOnlyChildInGroup(@NonNull NotificationEntry entry) {
         if (entry.getParent() == null) {
-            return false;
+            return false; // The entry is not attached.
         }
 
         return !isGroupSummary(entry) && entry.getParent().getChildren().size() == 1;
@@ -103,7 +106,7 @@ public class GroupMembershipManagerImpl implements GroupMembershipManager {
         return null;
     }
 
-    private boolean isEntryTopLevel(@NonNull NotificationEntry entry) {
+    private boolean isTopLevelEntry(@NonNull NotificationEntry entry) {
         return entry.getParent() == ROOT_ENTRY;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index 88994b9eec047f225c8b8c9c8e49cf8773590870..6ec9dbe003a2fa638e8ef3b048f12e7bb280b45e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -100,7 +100,11 @@ public interface NotificationInterruptStateProvider {
         /**
          * The notification is coming from a suspended packages, so FSI is suppressed.
          */
-        NO_FSI_SUSPENDED(false);
+        NO_FSI_SUSPENDED(false),
+        /**
+         * The device is not provisioned, launch FSI.
+         */
+        FSI_NOT_PROVISIONED(true);
 
         public final boolean shouldLaunch;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 0c43da066fdb8a7fb22d26bde6cf88a355ee7819..3819843aa7b27a5f660a921ff54c7db02bc52063 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -45,6 +45,7 @@ import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -75,6 +76,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
     private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
     private final UiEventLogger mUiEventLogger;
     private final UserTracker mUserTracker;
+    private final DeviceProvisionedController mDeviceProvisionedController;
 
     @VisibleForTesting
     protected boolean mUseHeadsUp = false;
@@ -121,7 +123,8 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
             NotifPipelineFlags flags,
             KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
             UiEventLogger uiEventLogger,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            DeviceProvisionedController deviceProvisionedController) {
         mContentResolver = contentResolver;
         mPowerManager = powerManager;
         mBatteryController = batteryController;
@@ -163,6 +166,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
                     headsUpObserver);
         }
         headsUpObserver.onChange(true); // set up
+        mDeviceProvisionedController = deviceProvisionedController;
     }
 
     @Override
@@ -334,6 +338,12 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
             }
         }
 
+        // The device is not provisioned, launch FSI.
+        if (!mDeviceProvisionedController.isDeviceProvisioned()) {
+            return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_NOT_PROVISIONED,
+                    suppressedByDND);
+        }
+
         // Detect the case determined by b/231322873 to launch FSI while device is in use,
         // as blocked by the correct implementation, and report the event.
         return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index cd6862113ee94f2f5a3f7a92f9fb4536dc195bc5..dc50990d002a28e953d1d5d77d7e9bf4b7a8137f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -71,6 +71,7 @@ import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.mapNotNull
@@ -181,6 +182,7 @@ class MobileConnectionRepositoryImpl(
                 telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
                 awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
             }
+            .flowOn(bgDispatcher)
             .scan(initial = initial) { state, event -> state.applyEvent(event) }
             .stateIn(scope = scope, started = SharingStarted.WhileSubscribed(), initial)
     }
@@ -358,6 +360,7 @@ class MobileConnectionRepositoryImpl(
 
                 awaitClose { context.unregisterReceiver(receiver) }
             }
+            .flowOn(bgDispatcher)
             .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
 
     override val dataEnabled = run {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 74a849a8c01ff241bb61a2f87a9d11ffe7055d95..ec54f08c9d7e32e85c25adc932a323ef0b8bc9eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -134,22 +134,24 @@ constructor(
             )
             .stateIn(scope, started = SharingStarted.WhileSubscribed(), null)
 
-    private val mobileSubscriptionsChangeEvent: Flow<Unit> = conflatedCallbackFlow {
-        val callback =
-            object : SubscriptionManager.OnSubscriptionsChangedListener() {
-                override fun onSubscriptionsChanged() {
-                    logger.logOnSubscriptionsChanged()
-                    trySend(Unit)
-                }
-            }
+    private val mobileSubscriptionsChangeEvent: Flow<Unit> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : SubscriptionManager.OnSubscriptionsChangedListener() {
+                        override fun onSubscriptionsChanged() {
+                            logger.logOnSubscriptionsChanged()
+                            trySend(Unit)
+                        }
+                    }
 
-        subscriptionManager.addOnSubscriptionsChangedListener(
-            bgDispatcher.asExecutor(),
-            callback,
-        )
+                subscriptionManager.addOnSubscriptionsChangedListener(
+                    bgDispatcher.asExecutor(),
+                    callback,
+                )
 
-        awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
-    }
+                awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
+            }
+            .flowOn(bgDispatcher)
 
     /**
      * State flow that emits the set of mobile data subscriptions, each represented by its own
@@ -184,6 +186,7 @@ constructor(
                 telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
                 awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
             }
+            .flowOn(bgDispatcher)
             .distinctUntilChanged()
             .logDiffsForTable(
                 tableLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 01fabcc8bc1e928fcfccc6873ccbc9084d4de360..3008c866d207b020d94fd4c096921c1de270424f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -159,7 +159,8 @@ public final class DeviceStateRotationLockSettingController
 
         // Update the rotation policy, if needed, for this new device state
         if (shouldBeLocked != isLocked) {
-            mRotationPolicyWrapper.setRotationLock(shouldBeLocked);
+            mRotationPolicyWrapper.setRotationLock(shouldBeLocked,
+                    /* caller= */"DeviceStateRotationLockSettingController#readPersistedSetting");
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
index 1158324567ffc3bbe232bd6ab023d7de8ea4c184..607f1e5624685a4b6ca6ff95f72f2294f59f44cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
@@ -24,8 +24,8 @@ public interface RotationLockController extends Listenable,
     boolean isRotationLockAffordanceVisible();
     boolean isRotationLocked();
     boolean isCameraRotationEnabled();
-    void setRotationLocked(boolean locked);
-    void setRotationLockedAtAngle(boolean locked, int rotation);
+    void setRotationLocked(boolean locked, String caller);
+    void setRotationLockedAtAngle(boolean locked, int rotation, String caller);
 
     public interface RotationLockControllerCallback {
         void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index 1eeb0ac8b3bbaa11269e75302852755fcad7889f..797aa1f3a3dd03930a1bc8ea127a79bfcc119e7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -93,12 +93,12 @@ public final class RotationLockControllerImpl implements RotationLockController
         return mRotationPolicy.isCameraRotationEnabled();
     }
 
-    public void setRotationLocked(boolean locked) {
-        mRotationPolicy.setRotationLock(locked);
+    public void setRotationLocked(boolean locked, String caller) {
+        mRotationPolicy.setRotationLock(locked, caller);
     }
 
-    public void setRotationLockedAtAngle(boolean locked, int rotation) {
-        mRotationPolicy.setRotationLockAtAngle(locked, rotation);
+    public void setRotationLockedAtAngle(boolean locked, int rotation, String caller) {
+        mRotationPolicy.setRotationLockAtAngle(locked, rotation, caller);
     }
 
     public boolean isRotationLockAffordanceVisible() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
index d8de07d185c6668ae70698b02175c3f4dfd37faa..374ebe0f28fbace5dc1286e86fa5b13c80786ecb 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
@@ -28,8 +28,8 @@ import javax.inject.Inject
  * Testable wrapper interface around RotationPolicy {link com.android.internal.view.RotationPolicy}
  */
 interface RotationPolicyWrapper {
-    fun setRotationLock(enabled: Boolean)
-    fun setRotationLockAtAngle(enabled: Boolean, rotation: Int)
+    fun setRotationLock(enabled: Boolean, caller: String)
+    fun setRotationLockAtAngle(enabled: Boolean, rotation: Int, caller: String)
     fun getRotationLockOrientation(): Int
     fun isRotationLockToggleVisible(): Boolean
     fun isRotationLocked(): Boolean
@@ -44,14 +44,14 @@ class RotationPolicyWrapperImpl @Inject constructor(
 ) :
         RotationPolicyWrapper {
 
-    override fun setRotationLock(enabled: Boolean) {
+    override fun setRotationLock(enabled: Boolean, caller: String) {
         traceSection("RotationPolicyWrapperImpl#setRotationLock") {
-            RotationPolicy.setRotationLock(context, enabled)
+            RotationPolicy.setRotationLock(context, enabled, caller)
         }
     }
 
-    override fun setRotationLockAtAngle(enabled: Boolean, rotation: Int) {
-        RotationPolicy.setRotationLockAtAngle(context, enabled, rotation)
+    override fun setRotationLockAtAngle(enabled: Boolean, rotation: Int, caller: String) {
+        RotationPolicy.setRotationLockAtAngle(context, enabled, rotation, caller)
     }
 
     override fun getRotationLockOrientation(): Int =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index fc7d20aeb3565dd6b502baf434c5c15220f02fb5..874053a8365436952326cd3d50a20fbb3385702c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -236,7 +236,8 @@ class AuthenticationInteractorTest : SysuiTestCase() {
             utils.authenticationRepository.setAuthenticationMethod(
                 DataLayerAuthenticationMethodModel.Pin
             )
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(isThrottled).isFalse()
         }
 
@@ -246,7 +247,8 @@ class AuthenticationInteractorTest : SysuiTestCase() {
             utils.authenticationRepository.setAuthenticationMethod(
                 DataLayerAuthenticationMethodModel.Pin
             )
-            assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))).isFalse()
+            assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4)))
+                .isEqualTo(AuthenticationResult.FAILED)
         }
 
     @Test(expected = IllegalArgumentException::class)
@@ -267,7 +269,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                 overrideCredential(pin)
             }
 
-            assertThat(underTest.authenticate(pin)).isTrue()
+            assertThat(underTest.authenticate(pin)).isEqualTo(AuthenticationResult.SUCCEEDED)
         }
 
     @Test
@@ -282,7 +284,8 @@ class AuthenticationInteractorTest : SysuiTestCase() {
             utils.authenticationRepository.setAuthenticationMethod(
                 DataLayerAuthenticationMethodModel.Pin
             )
-            assertThat(underTest.authenticate(List(17) { 9 })).isFalse()
+            assertThat(underTest.authenticate(List(17) { 9 }))
+                .isEqualTo(AuthenticationResult.FAILED)
         }
 
     @Test
@@ -293,7 +296,8 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                 DataLayerAuthenticationMethodModel.Password
             )
 
-            assertThat(underTest.authenticate("password".toList())).isTrue()
+            assertThat(underTest.authenticate("password".toList()))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(isThrottled).isFalse()
         }
 
@@ -304,7 +308,8 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                 DataLayerAuthenticationMethodModel.Password
             )
 
-            assertThat(underTest.authenticate("alohomora".toList())).isFalse()
+            assertThat(underTest.authenticate("alohomora".toList()))
+                .isEqualTo(AuthenticationResult.FAILED)
         }
 
     @Test
@@ -314,7 +319,8 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                 DataLayerAuthenticationMethodModel.Pattern
             )
 
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
         }
 
     @Test
@@ -327,22 +333,14 @@ class AuthenticationInteractorTest : SysuiTestCase() {
             assertThat(
                     underTest.authenticate(
                         listOf(
-                            AuthenticationPatternCoordinate(
-                                x = 2,
-                                y = 0,
-                            ),
-                            AuthenticationPatternCoordinate(
-                                x = 2,
-                                y = 1,
-                            ),
-                            AuthenticationPatternCoordinate(
-                                x = 2,
-                                y = 2,
-                            ),
+                            AuthenticationPatternCoordinate(x = 2, y = 0),
+                            AuthenticationPatternCoordinate(x = 2, y = 1),
+                            AuthenticationPatternCoordinate(x = 2, y = 2),
+                            AuthenticationPatternCoordinate(x = 1, y = 2),
                         )
                     )
                 )
-                .isFalse()
+                .isEqualTo(AuthenticationResult.FAILED)
         }
 
     @Test
@@ -361,7 +359,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                         tryAutoConfirm = true
                     )
                 )
-                .isNull()
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(isThrottled).isFalse()
         }
 
@@ -379,7 +377,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                         tryAutoConfirm = true
                     )
                 )
-                .isFalse()
+                .isEqualTo(AuthenticationResult.FAILED)
             assertThat(isUnlocked).isFalse()
         }
 
@@ -397,7 +395,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                         tryAutoConfirm = true
                     )
                 )
-                .isFalse()
+                .isEqualTo(AuthenticationResult.FAILED)
             assertThat(isUnlocked).isFalse()
         }
 
@@ -415,7 +413,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                         tryAutoConfirm = true
                     )
                 )
-                .isTrue()
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(isUnlocked).isTrue()
         }
 
@@ -433,7 +431,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                         tryAutoConfirm = true
                     )
                 )
-                .isNull()
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(isUnlocked).isFalse()
         }
 
@@ -445,7 +443,8 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                 DataLayerAuthenticationMethodModel.Password
             )
 
-            assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)).isNull()
+            assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true))
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(isUnlocked).isFalse()
         }
 
@@ -490,7 +489,8 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                 )
 
             // Correct PIN, but throttled, so doesn't attempt it:
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isNull()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(isUnlocked).isFalse()
             assertThat(isThrottled).isTrue()
             assertThat(throttling)
@@ -536,7 +536,8 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                 )
 
             // Correct PIN and no longer throttled so unlocks successfully:
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(isUnlocked).isTrue()
             assertThat(isThrottled).isFalse()
             assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 77d8102fff2e4a4d40ef87a73f7594f6a8d1d42f..92c8a395a696c6d19e4966504cec873eb66ed599 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -17,13 +17,14 @@
 package com.android.systemui.bouncer.domain.interactor
 
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.AuthenticationResult
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -87,7 +88,8 @@ class BouncerInteractorTest : SysuiTestCase() {
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
 
             // Wrong input.
-            assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
+            assertThat(underTest.authenticate(listOf(9, 8, 7)))
+                .isEqualTo(AuthenticationResult.FAILED)
             assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
@@ -95,7 +97,8 @@ class BouncerInteractorTest : SysuiTestCase() {
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
 
             // Correct input.
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
@@ -115,13 +118,14 @@ class BouncerInteractorTest : SysuiTestCase() {
             underTest.clearMessage()
 
             // Incomplete input.
-            assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true)).isNull()
+            assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true))
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(message).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             // Wrong 6-digit pin
             assertThat(underTest.authenticate(listOf(1, 2, 3, 5, 5, 6), tryAutoConfirm = true))
-                .isFalse()
+                .isEqualTo(AuthenticationResult.FAILED)
             assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
@@ -132,7 +136,7 @@ class BouncerInteractorTest : SysuiTestCase() {
                         tryAutoConfirm = true
                     )
                 )
-                .isTrue()
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
@@ -150,7 +154,8 @@ class BouncerInteractorTest : SysuiTestCase() {
             underTest.clearMessage()
 
             // Incomplete input.
-            assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true)).isNull()
+            assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true))
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(message).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
@@ -161,7 +166,7 @@ class BouncerInteractorTest : SysuiTestCase() {
                         tryAutoConfirm = true
                     )
                 )
-                .isNull()
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(message).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
@@ -187,7 +192,8 @@ class BouncerInteractorTest : SysuiTestCase() {
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
 
             // Wrong input.
-            assertThat(underTest.authenticate("alohamora".toList())).isFalse()
+            assertThat(underTest.authenticate("alohamora".toList()))
+                .isEqualTo(AuthenticationResult.FAILED)
             assertThat(message).isEqualTo(MESSAGE_WRONG_PASSWORD)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
@@ -195,7 +201,8 @@ class BouncerInteractorTest : SysuiTestCase() {
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
 
             // Correct input.
-            assertThat(underTest.authenticate("password".toList())).isTrue()
+            assertThat(underTest.authenticate("password".toList()))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
@@ -220,8 +227,30 @@ class BouncerInteractorTest : SysuiTestCase() {
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
 
             // Wrong input.
-            assertThat(underTest.authenticate(listOf(AuthenticationPatternCoordinate(1, 2))))
-                .isFalse()
+            val wrongPattern =
+                listOf(
+                    AuthenticationPatternCoordinate(1, 2),
+                    AuthenticationPatternCoordinate(1, 1),
+                    AuthenticationPatternCoordinate(0, 0),
+                    AuthenticationPatternCoordinate(0, 1),
+                )
+            assertThat(wrongPattern).isNotEqualTo(FakeAuthenticationRepository.PATTERN)
+            assertThat(wrongPattern.size).isAtLeast(utils.authenticationRepository.minPatternLength)
+            assertThat(underTest.authenticate(wrongPattern)).isEqualTo(AuthenticationResult.FAILED)
+            assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+            underTest.resetMessage()
+            assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
+
+            // Too short input.
+            val tooShortPattern =
+                FakeAuthenticationRepository.PATTERN.subList(
+                    0,
+                    utils.authenticationRepository.minPatternLength - 1
+                )
+            assertThat(underTest.authenticate(tooShortPattern))
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
@@ -229,7 +258,8 @@ class BouncerInteractorTest : SysuiTestCase() {
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
 
             // Correct input.
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
@@ -294,7 +324,8 @@ class BouncerInteractorTest : SysuiTestCase() {
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times ->
                 // Wrong PIN.
-                assertThat(underTest.authenticate(listOf(6, 7, 8, 9))).isFalse()
+                assertThat(underTest.authenticate(listOf(6, 7, 8, 9)))
+                    .isEqualTo(AuthenticationResult.FAILED)
                 if (
                     times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
                 ) {
@@ -317,7 +348,8 @@ class BouncerInteractorTest : SysuiTestCase() {
             )
 
             // Correct PIN, but throttled, so doesn't change away from the bouncer scene:
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isNull()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+                .isEqualTo(AuthenticationResult.SKIPPED)
             assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
             assertTryAgainMessage(
                 message,
@@ -347,7 +379,8 @@ class BouncerInteractorTest : SysuiTestCase() {
             assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
 
             // Correct PIN and no longer throttled so changes to the Gone scene:
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
             assertThat(currentScene?.key).isEqualTo(SceneKey.Gone)
             assertThat(isThrottled).isFalse()
             assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
@@ -362,7 +395,7 @@ class BouncerInteractorTest : SysuiTestCase() {
             val bouncerSceneKey = currentScene?.key
             assertThat(bouncerSceneKey).isEqualTo(SceneKey.Bouncer)
 
-            underTest.hide("")
+            underTest.onImeHidden()
 
             assertThat(currentScene?.key).isEqualTo(SceneKey.Lockscreen)
         }
@@ -376,7 +409,7 @@ class BouncerInteractorTest : SysuiTestCase() {
             val notBouncerSceneKey = currentScene?.key
             assertThat(notBouncerSceneKey).isNotEqualTo(SceneKey.Bouncer)
 
-            underTest.hide("")
+            underTest.onImeHidden()
 
             assertThat(currentScene?.key).isEqualTo(notBouncerSceneKey)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 9011c2f296c3753694e57a164cb5640880c03cc7..2f7dde02fdceeece0fd7afaf801ba01566fb5c48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -45,7 +45,7 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() {
     private val underTest =
         PinBouncerViewModel(
             applicationContext = context,
-            applicationScope = testScope.backgroundScope,
+            viewModelScope = testScope.backgroundScope,
             interactor =
                 utils.bouncerInteractor(
                     authenticationInteractor = authenticationInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 2c96bcc9dd337604417e09950c0398cb3e540bc7..da2534d6fb1471e9513c5ef397d96c27186f8a55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -46,7 +46,7 @@ class BouncerViewModelTest : SysuiTestCase() {
     private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
-            repository = utils.authenticationRepository(),
+            repository = utils.authenticationRepository,
         )
     private val bouncerInteractor =
         utils.bouncerInteractor(
@@ -66,7 +66,8 @@ class BouncerViewModelTest : SysuiTestCase() {
 
             authMethodsToTest().forEach { authMethod ->
                 utils.authenticationRepository.setAuthenticationMethod(authMethod)
-                val job = underTest.authMethod.onEach { authMethodViewModel = it }.launchIn(this)
+                val job =
+                    underTest.authMethodViewModel.onEach { authMethodViewModel = it }.launchIn(this)
                 runCurrent()
 
                 if (authMethod.isSecure) {
@@ -86,22 +87,43 @@ class BouncerViewModelTest : SysuiTestCase() {
         }
 
     @Test
-    fun authMethod_reusesInstances() =
+    fun authMethodChanged_doesNotReuseInstances() =
         testScope.runTest {
             val seen =
                 mutableMapOf<DomainLayerAuthenticationMethodModel, AuthMethodBouncerViewModel>()
             val authMethodViewModel: AuthMethodBouncerViewModel? by
-                collectLastValue(underTest.authMethod)
+                collectLastValue(underTest.authMethodViewModel)
+
             // First pass, populate our "seen" map:
             authMethodsToTest().forEach { authMethod ->
                 utils.authenticationRepository.setAuthenticationMethod(authMethod)
                 authMethodViewModel?.let { seen[authMethod] = it }
             }
 
-            // Second pass, assert same instances are reused:
+            // Second pass, assert same instances are not reused:
+            authMethodsToTest().forEach { authMethod ->
+                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                authMethodViewModel?.let {
+                    assertThat(it.authenticationMethod).isEqualTo(authMethod)
+                    assertThat(it).isNotSameInstanceAs(seen[authMethod])
+                }
+            }
+        }
+
+    @Test
+    fun authMethodUnchanged_reusesInstances() =
+        testScope.runTest {
             authMethodsToTest().forEach { authMethod ->
                 utils.authenticationRepository.setAuthenticationMethod(authMethod)
-                authMethodViewModel?.let { assertThat(it).isSameInstanceAs(seen[authMethod]) }
+                val firstInstance: AuthMethodBouncerViewModel? =
+                    collectLastValue(underTest.authMethodViewModel).invoke()
+
+                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                val secondInstance: AuthMethodBouncerViewModel? =
+                    collectLastValue(underTest.authMethodViewModel).invoke()
+
+                firstInstance?.let { assertThat(it.authenticationMethod).isEqualTo(authMethod) }
+                assertThat(secondInstance).isSameInstanceAs(firstInstance)
             }
         }
 
@@ -136,7 +158,7 @@ class BouncerViewModelTest : SysuiTestCase() {
         testScope.runTest {
             val isInputEnabled by
                 collectLastValue(
-                    underTest.authMethod.flatMapLatest { authViewModel ->
+                    underTest.authMethodViewModel.flatMapLatest { authViewModel ->
                         authViewModel?.isInputEnabled ?: emptyFlow()
                     }
                 )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 3375184c1cf6003f8401e2065840f63adb2ed210..c1b33542267b763659f25000f6bfaf81f9a5654a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -17,10 +17,11 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainAuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -28,6 +29,7 @@ import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -59,7 +61,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
         )
     private val underTest =
         PasswordBouncerViewModel(
-            applicationScope = testScope.backgroundScope,
+            viewModelScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
         )
@@ -76,19 +78,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-
-            underTest.onShown()
+            lockDeviceAndOpenPasswordBouncer()
 
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD)
             assertThat(password).isEqualTo("")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            assertThat(underTest.authenticationMethod)
+                .isEqualTo(DomainAuthenticationMethodModel.Password)
         }
 
     @Test
@@ -97,15 +93,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
-            runCurrent()
+            lockDeviceAndOpenPasswordBouncer()
 
             underTest.onPasswordInputChanged("password")
 
@@ -118,16 +106,9 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
     fun onAuthenticateKeyPressed_whenCorrect() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
-            underTest.onPasswordInputChanged("password")
+            lockDeviceAndOpenPasswordBouncer()
 
+            underTest.onPasswordInputChanged("password")
             underTest.onAuthenticateKeyPressed()
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
@@ -139,16 +120,9 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
-            underTest.onPasswordInputChanged("wrong")
+            lockDeviceAndOpenPasswordBouncer()
 
+            underTest.onPasswordInputChanged("wrong")
             underTest.onAuthenticateKeyPressed()
 
             assertThat(password).isEqualTo("")
@@ -185,14 +159,9 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
+            lockDeviceAndOpenPasswordBouncer()
+
+            // Enter the wrong password:
             underTest.onPasswordInputChanged("wrong")
             underTest.onAuthenticateKeyPressed()
             assertThat(password).isEqualTo("")
@@ -213,14 +182,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val password by collectLastValue(underTest.password)
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
+            lockDeviceAndOpenPasswordBouncer()
 
             // The user types a password.
             underTest.onPasswordInputChanged("password")
@@ -243,6 +205,18 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
+    private fun TestScope.lockDeviceAndOpenPasswordBouncer() {
+        utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password)
+        utils.authenticationRepository.setUnlocked(false)
+        sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+        sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
+        assertThat(collectLastValue(sceneInteractor.desiredScene).invoke())
+            .isEqualTo(SceneModel(SceneKey.Bouncer))
+        underTest.onShown()
+        runCurrent()
+    }
+
     companion object {
         private const val ENTER_YOUR_PASSWORD = "Enter your password"
         private const val WRONG_PASSWORD = "Wrong password"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 102cfe21838e6d289698594ecfdce970130e8b60..bf109d9b2b6181c42ecc4ebf6086ca1d3a201114 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -17,12 +17,13 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainAuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -64,7 +65,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
     private val underTest =
         PatternBouncerViewModel(
             applicationContext = context,
-            applicationScope = testScope.backgroundScope,
+            viewModelScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
         )
@@ -85,14 +86,14 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            transitionToPatternBouncer()
-
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
 
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PATTERN)
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            assertThat(underTest.authenticationMethod)
+                .isEqualTo(DomainAuthenticationMethodModel.Pattern)
         }
 
     @Test
@@ -102,9 +103,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            transitionToPatternBouncer()
-            underTest.onShown()
-            runCurrent()
+            lockDeviceAndOpenPatternBouncer()
 
             underTest.onDragStart()
 
@@ -120,8 +119,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
             underTest.onDragStart()
             assertThat(currentDot).isNull()
             CORRECT_PATTERN.forEachIndexed { index, coordinate ->
@@ -158,8 +156,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
             underTest.onDragStart()
             CORRECT_PATTERN.subList(0, 3).forEach { coordinate -> dragToCoordinate(coordinate) }
 
@@ -175,8 +172,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
     fun onDrag_shouldIncludeDotsThatWereSkippedOverAlongTheSameRow() =
         testScope.runTest {
             val selectedDots by collectLastValue(underTest.selectedDots)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
 
             /*
              * Pattern setup, coordinates are (column, row)
@@ -202,8 +198,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
     fun onDrag_shouldIncludeDotsThatWereSkippedOverAlongTheSameColumn() =
         testScope.runTest {
             val selectedDots by collectLastValue(underTest.selectedDots)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
 
             /*
              * Pattern setup, coordinates are (column, row)
@@ -229,8 +224,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
     fun onDrag_shouldIncludeDotsThatWereSkippedOverAlongTheDiagonal() =
         testScope.runTest {
             val selectedDots by collectLastValue(underTest.selectedDots)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
 
             /*
              * Pattern setup
@@ -258,8 +252,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
     fun onDrag_shouldNotIncludeDotIfItIsNotOnTheLine() =
         testScope.runTest {
             val selectedDots by collectLastValue(underTest.selectedDots)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
 
             /*
              * Pattern setup
@@ -287,8 +280,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
     fun onDrag_shouldNotIncludeSkippedOverDotsIfTheyAreAlreadySelected() =
         testScope.runTest {
             val selectedDots by collectLastValue(underTest.selectedDots)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
 
             /*
              * Pattern setup
@@ -315,20 +307,10 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
     @Test
     fun onDragEnd_whenPatternTooShort() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
-            val selectedDots by collectLastValue(underTest.selectedDots)
-            val currentDot by collectLastValue(underTest.currentDot)
             val throttlingDialogMessage by
                 collectLastValue(bouncerViewModel.throttlingDialogMessage)
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern
-            )
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
 
             // Enter a pattern that's too short more than enough times that would normally trigger
             // throttling if the pattern were not too short and wrong:
@@ -337,7 +319,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
                 underTest.onDragStart()
                 CORRECT_PATTERN.subList(
                         0,
-                        authenticationInteractor.minPatternLength - 1,
+                        utils.authenticationRepository.minPatternLength - 1,
                     )
                     .forEach { coordinate ->
                         underTest.onDrag(
@@ -362,10 +344,10 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            transitionToPatternBouncer()
-            underTest.onShown()
+            lockDeviceAndOpenPatternBouncer()
+
             underTest.onDragStart()
-            CORRECT_PATTERN.subList(2, 7).forEach { coordinate -> dragToCoordinate(coordinate) }
+            CORRECT_PATTERN.subList(2, 7).forEach(::dragToCoordinate)
             underTest.onDragEnd()
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
@@ -373,7 +355,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             // Enter the correct pattern:
-            CORRECT_PATTERN.forEach { coordinate -> dragToCoordinate(coordinate) }
+            CORRECT_PATTERN.forEach(::dragToCoordinate)
 
             underTest.onDragEnd()
 
@@ -382,7 +364,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
 
     private fun dragOverCoordinates(vararg coordinatesDragged: Point) {
         underTest.onDragStart()
-        coordinatesDragged.forEach { dragToCoordinate(it) }
+        coordinatesDragged.forEach(::dragToCoordinate)
     }
 
     private fun dragToCoordinate(coordinate: Point) {
@@ -394,13 +376,15 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
         )
     }
 
-    private fun TestScope.transitionToPatternBouncer() {
+    private fun TestScope.lockDeviceAndOpenPatternBouncer() {
         utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern)
         utils.authenticationRepository.setUnlocked(false)
         sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
         sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
         assertThat(collectLastValue(sceneInteractor.desiredScene).invoke())
             .isEqualTo(SceneModel(SceneKey.Bouncer))
+        underTest.onShown()
+        runCurrent()
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 35238ce00057f181875fc020464ab1a978af389d..2576204c247fb1bbc3c95cd9c0191cef586cc41a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -17,11 +17,12 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainAuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -30,6 +31,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -62,7 +64,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
     private val underTest =
         PinBouncerViewModel(
             applicationContext = context,
-            applicationScope = testScope.backgroundScope,
+            viewModelScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
         )
@@ -90,6 +92,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PIN)
             assertThat(pin).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            assertThat(underTest.authenticationMethod)
+                .isEqualTo(DomainAuthenticationMethodModel.Pin)
         }
 
     @Test
@@ -120,14 +124,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            lockDeviceAndOpenPinBouncer()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
-            runCurrent()
             underTest.onPinButtonClicked(1)
             assertThat(pin).hasSize(1)
 
@@ -141,15 +139,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
     @Test
     fun onPinEdit() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
+            lockDeviceAndOpenPinBouncer()
 
             underTest.onPinButtonClicked(1)
             underTest.onPinButtonClicked(2)
@@ -168,18 +159,13 @@ class PinBouncerViewModelTest : SysuiTestCase() {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            lockDeviceAndOpenPinBouncer()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
-            runCurrent()
             underTest.onPinButtonClicked(1)
             underTest.onPinButtonClicked(2)
             underTest.onPinButtonClicked(3)
             underTest.onPinButtonClicked(4)
+            runCurrent()
 
             underTest.onBackspaceButtonLongPressed()
 
@@ -192,13 +178,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
     fun onAuthenticateButtonClicked_whenCorrect() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            lockDeviceAndOpenPinBouncer()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
             FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
                 underTest.onPinButtonClicked(digit)
             }
@@ -214,13 +195,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            lockDeviceAndOpenPinBouncer()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
             underTest.onPinButtonClicked(1)
             underTest.onPinButtonClicked(2)
             underTest.onPinButtonClicked(3)
@@ -240,13 +216,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            lockDeviceAndOpenPinBouncer()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
             underTest.onPinButtonClicked(1)
             underTest.onPinButtonClicked(2)
             underTest.onPinButtonClicked(3)
@@ -272,14 +243,9 @@ class PinBouncerViewModelTest : SysuiTestCase() {
     fun onAutoConfirm_whenCorrect() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
             utils.authenticationRepository.setAutoConfirmEnabled(true)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            lockDeviceAndOpenPinBouncer()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
             FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
                 underTest.onPinButtonClicked(digit)
             }
@@ -293,14 +259,9 @@ class PinBouncerViewModelTest : SysuiTestCase() {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
             utils.authenticationRepository.setAutoConfirmEnabled(true)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            lockDeviceAndOpenPinBouncer()
 
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
             FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
                 underTest.onPinButtonClicked(digit)
             }
@@ -318,13 +279,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-            underTest.onShown()
+            lockDeviceAndOpenPinBouncer()
 
             // The user types a PIN.
             FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
@@ -401,6 +356,18 @@ class PinBouncerViewModelTest : SysuiTestCase() {
             assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
         }
 
+    private fun TestScope.lockDeviceAndOpenPinBouncer() {
+        utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+        utils.authenticationRepository.setUnlocked(false)
+        sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+        sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
+        assertThat(collectLastValue(sceneInteractor.desiredScene).invoke())
+            .isEqualTo(SceneModel(SceneKey.Bouncer))
+        underTest.onShown()
+        runCurrent()
+    }
+
     companion object {
         private const val ENTER_YOUR_PIN = "Enter your pin"
         private const val WRONG_PIN = "Wrong pin"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandlerTest.kt
index 49f536e26ce639f78409e63efa3ebc55143714c8..74b3fce1279088a1e958cf96af6e7bdc1c14875b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandlerTest.kt
@@ -85,6 +85,7 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
     fun testCarouselScroll_shortScroll() {
         whenever(mediaCarousel.isLayoutRtl).thenReturn(false)
         whenever(mediaCarousel.relativeScrollX).thenReturn(300)
+        whenever(mediaCarousel.scrollX).thenReturn(300)
 
         mediaCarousel.touchListener?.onTouchEvent(motionEventUp)
         executor.runAllReady()
@@ -96,6 +97,7 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
     fun testCarouselScroll_shortScroll_isRTL() {
         whenever(mediaCarousel.isLayoutRtl).thenReturn(true)
         whenever(mediaCarousel.relativeScrollX).thenReturn(300)
+        whenever(mediaCarousel.scrollX).thenReturn(carouselWidth - 300)
 
         mediaCarousel.touchListener?.onTouchEvent(motionEventUp)
         executor.runAllReady()
@@ -107,6 +109,7 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
     fun testCarouselScroll_longScroll() {
         whenever(mediaCarousel.isLayoutRtl).thenReturn(false)
         whenever(mediaCarousel.relativeScrollX).thenReturn(600)
+        whenever(mediaCarousel.scrollX).thenReturn(600)
 
         mediaCarousel.touchListener?.onTouchEvent(motionEventUp)
         executor.runAllReady()
@@ -118,6 +121,7 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
     fun testCarouselScroll_longScroll_isRTL() {
         whenever(mediaCarousel.isLayoutRtl).thenReturn(true)
         whenever(mediaCarousel.relativeScrollX).thenReturn(600)
+        whenever(mediaCarousel.scrollX).thenReturn(carouselWidth - 600)
 
         mediaCarousel.touchListener?.onTouchEvent(motionEventUp)
         executor.runAllReady()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index bce4c06d320f4fc09afe6268c5640169e9cc1013..3bf59ca6202408e984110c8d4f77d15ff33d2585 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -172,6 +172,9 @@ class DeviceControlsTileTest : SysuiTestCase() {
     @Test
     fun testNotAvailableControls() {
         featureEnabled = false
+
+        // Destroy previous tile
+        tile.destroy()
         tile = createTile()
 
         assertThat(tile.isAvailable).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
index a0c107340dcd35f2edef6bd5c32322a7da68e19e..954d30edf1438e900b6bc51a70f06240757f790a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
@@ -226,6 +226,10 @@ public class DreamTileTest extends SysuiTestCase {
         assertTrue(supportedTileOnlySystemUser.isAvailable());
         when(mUserTracker.getUserInfo()).thenReturn(nonMainUserInfo);
         assertFalse(supportedTileOnlySystemUser.isAvailable());
+
+        destroyTile(unsupportedTile);
+        destroyTile(supportedTileAllUsers);
+        destroyTile(supportedTileOnlySystemUser);
     }
 
     @Test
@@ -250,6 +254,8 @@ public class DreamTileTest extends SysuiTestCase {
         mTestableLooper.processAllMessages();
         assertEquals(QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_screen_saver_undocked),
                 dockedTile.getState().icon);
+
+        destroyTile(dockedTile);
     }
 
     private void setScreensaverEnabled(boolean enabled) {
@@ -257,6 +263,11 @@ public class DreamTileTest extends SysuiTestCase {
                 DEFAULT_USER);
     }
 
+    private void destroyTile(QSTileImpl<?> tile) {
+        tile.destroy();
+        mTestableLooper.processAllMessages();
+    }
+
     private DreamTile constructTileForTest(boolean dreamSupported,
             boolean dreamOnlyEnabledForSystemUser) {
         return new DreamTile(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index df6993d11447d06a6b6fc587281baac99679eaaf..440270b6ebfa29a3db6490057e3047634170fac8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -216,7 +216,7 @@ public class RotationLockTileTest extends SysuiTestCase {
     public void testSecondaryString_rotationResolverDisabled_isEmpty() {
         mTestableResources.addOverride(com.android.internal.R.bool.config_allowRotationResolver,
                 false);
-        mLockTile = new RotationLockTile(
+        RotationLockTile otherTile = new RotationLockTile(
                 mHost,
                 mUiEventLogger,
                 mTestableLooper.getLooper(),
@@ -232,10 +232,12 @@ public class RotationLockTileTest extends SysuiTestCase {
                 new FakeSettings()
         );
 
-        mLockTile.refreshState();
+        otherTile.refreshState();
         mTestableLooper.processAllMessages();
 
-        assertEquals("", mLockTile.getState().secondaryLabel.toString());
+        assertEquals("", otherTile.getState().secondaryLabel.toString());
+
+        destroyTile(otherTile);
     }
 
     @Test
@@ -258,6 +260,12 @@ public class RotationLockTileTest extends SysuiTestCase {
         assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_auto_rotate_icon_on));
     }
 
+
+    private void destroyTile(QSTileImpl<?> tile) {
+        tile.destroy();
+        mTestableLooper.processAllMessages();
+    }
+
     private void enableAutoRotation() {
         when(mRotationPolicyWrapper.isRotationLocked()).thenReturn(false);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 6b918c6ba15b5c5ea40f5b83e7b101ab57f46046..85bd92bf3b12bd1264e0d55f438da76bb58383aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -76,6 +76,7 @@ import org.junit.runners.JUnit4
  *   being used when the state is as required (e.g. cannot unlock an already unlocked device, cannot
  *   put to sleep a device that's already asleep, etc.).
  */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class SceneFrameworkIntegrationTest : SysuiTestCase() {
@@ -481,7 +482,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
         bouncerSceneJob =
             if (to.key == SceneKey.Bouncer) {
                 testScope.backgroundScope.launch {
-                    bouncerViewModel.authMethod.collect {
+                    bouncerViewModel.authMethodViewModel.collect {
                         // Do nothing. Need this to turn this otherwise cold flow, hot.
                     }
                 }
@@ -556,7 +557,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
         assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
             .that(getCurrentSceneInUi())
             .isEqualTo(SceneKey.Bouncer)
-        val authMethodViewModel by collectLastValue(bouncerViewModel.authMethod)
+        val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel)
         assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
             .that(authMethodViewModel)
             .isInstanceOf(PinBouncerViewModel::class.java)
@@ -613,11 +614,12 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
     private fun TestScope.dismissIme(
         showImeBeforeDismissing: Boolean = true,
     ) {
-        if (showImeBeforeDismissing) {
-            bouncerViewModel.authMethod.value?.onImeVisibilityChanged(true)
+        bouncerViewModel.authMethodViewModel.value?.apply {
+            if (showImeBeforeDismissing) {
+                onImeVisibilityChanged(true)
+            }
+            onImeVisibilityChanged(false)
+            runCurrent()
         }
-
-        bouncerViewModel.authMethod.value?.onImeVisibilityChanged(false)
-        runCurrent()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
index ac2aec6e0c867054cda3466509977217cf960946..0a10b2c85ebe2ea836383bc2e9711023f362eb59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
@@ -31,6 +31,7 @@ import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mockito.never
@@ -125,6 +126,22 @@ class GroupExpansionManagerTest : SysuiTestCase() {
         assertThat(listenerCalledCount).isEqualTo(5)
     }
 
+    @Test
+    fun testExpandUnattachedEntry() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+
+        // First, expand the entry when it is attached.
+        gem.setGroupExpanded(summary1, true)
+        assertThat(gem.isGroupExpanded(summary1)).isTrue()
+
+        // Un-attach it, and un-expand it.
+        NotificationEntryBuilder.setNewParent(summary1, null)
+        gem.setGroupExpanded(summary1, false)
+
+        // Expanding again should throw.
+        assertThrows(IllegalArgumentException::class.java) { gem.setGroupExpanded(summary1, true) }
+    }
+
     @Test
     fun testSyncWithPipeline() {
         featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
index 37ec0e09f8410812fceb3ef95ac0dc0d7e16f663..c1ffa641c6a4a63afa383f7302b574f7863ab030 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
@@ -58,6 +58,35 @@ class GroupMembershipManagerTest : SysuiTestCase() {
         val noParentEntry = NotificationEntryBuilder().setParent(null).build()
         assertThat(gmm.isChildInGroup(noParentEntry)).isFalse()
     }
+    @Test
+    fun testIsChildInGroup_summary_old() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
+
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+        assertThat(gmm.isChildInGroup(summary)).isTrue()
+    }
+
+    @Test
+    fun testIsChildInGroup_summary_new() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+        assertThat(gmm.isChildInGroup(summary)).isFalse()
+    }
 
     @Test
     fun testIsChildInGroup_child() {
@@ -67,40 +96,78 @@ class GroupMembershipManagerTest : SysuiTestCase() {
     }
 
     @Test
-    fun testIsGroupSummary() {
+    fun testIsGroupSummary_topLevelEntry() {
         featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-        val entry = NotificationEntryBuilder().setGroupSummary(mContext, true).build()
-        assertThat(gmm.isGroupSummary(entry)).isTrue()
+        val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+        assertThat(gmm.isGroupSummary(entry)).isFalse()
     }
 
     @Test
-    fun testGetGroupSummary() {
+    fun testIsGroupSummary_summary() {
         featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
 
+        val groupKey = "group"
         val summary =
             NotificationEntryBuilder()
-                .setGroup(mContext, "group")
+                .setGroup(mContext, groupKey)
                 .setGroupSummary(mContext, true)
                 .build()
-        val groupEntry =
-            GroupEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).setSummary(summary).build()
-        val entry =
-            NotificationEntryBuilder().setGroup(mContext, "group").setParent(groupEntry).build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
 
-        assertThat(gmm.getGroupSummary(entry)).isEqualTo(summary)
+        assertThat(gmm.isGroupSummary(summary)).isTrue()
     }
 
     @Test
-    fun testGetGroupSummary_isSummary_old() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-        val entry = NotificationEntryBuilder().setGroupSummary(mContext, true).build()
+    fun testIsGroupSummary_child() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+        assertThat(gmm.isGroupSummary(entry)).isFalse()
+    }
+
+    @Test
+    fun testGetGroupSummary_topLevelEntry() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+        val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
         assertThat(gmm.getGroupSummary(entry)).isNull()
     }
 
     @Test
-    fun testGetGroupSummary_isSummary_new() {
+    fun testGetGroupSummary_summary() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+        assertThat(gmm.getGroupSummary(summary)).isEqualTo(summary)
+    }
+
+    @Test
+    fun testGetGroupSummary_child() {
         featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-        val entry = NotificationEntryBuilder().setGroupSummary(mContext, true).build()
-        assertThat(gmm.getGroupSummary(entry)).isEqualTo(entry)
+
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+        assertThat(gmm.getGroupSummary(entry)).isEqualTo(summary)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index f05436f66d823b0e9300fdaac8002492352fc1ad..50ce265b67d16152eabd33932c0a48667b33018a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -71,6 +71,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent;
 import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -117,6 +118,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
     PendingIntent mPendingIntent;
     @Mock
     UserTracker mUserTracker;
+    @Mock
+    DeviceProvisionedController mDeviceProvisionedController;
 
     private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider;
 
@@ -141,7 +144,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
                         mFlags,
                         mKeyguardNotificationVisibilityProvider,
                         mUiEventLoggerFake,
-                        mUserTracker);
+                        mUserTracker,
+                        mDeviceProvisionedController);
         mNotifInterruptionStateProvider.mUseHeadsUp = true;
     }
 
@@ -693,6 +697,25 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
         verify(mLogger).logFullscreen(entry, "FSI_KEYGUARD_SHOWING");
     }
 
+    @Test
+    public void testShouldFullscreen_suppressedInterruptionsWhenNotProvisioned() {
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+        when(mPowerManager.isInteractive()).thenReturn(true);
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
+        when(mPowerManager.isScreenOn()).thenReturn(true);
+        when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(false);
+        mNotifInterruptionStateProvider.addSuppressor(mSuppressInterruptions);
+
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+                .isEqualTo(FullScreenIntentDecision.FSI_NOT_PROVISIONED);
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+                .isTrue();
+        verify(mLogger, never()).logNoFullscreen(any(), any());
+        verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+        verify(mLogger).logFullscreen(entry, "FSI_NOT_PROVISIONED");
+    }
+
     @Test
     public void testShouldNotFullScreen_willHun() throws RemoteException {
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 5c3dde59596e26c62a37c4f8edb9d2c25db9ae18..0b171b2a23e40b127740fbc2813fa9e4791009d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -364,7 +364,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
                         mock(NotifPipelineFlags.class),
                         mock(KeyguardNotificationVisibilityProvider.class),
                         mock(UiEventLogger.class),
-                        mUserTracker);
+                        mUserTracker,
+                        mDeviceProvisionedController);
 
         mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
         mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
@@ -1169,7 +1170,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
                 NotifPipelineFlags flags,
                 KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
                 UiEventLogger uiEventLogger,
-                UserTracker userTracker) {
+                UserTracker userTracker,
+                DeviceProvisionedController deviceProvisionedController) {
             super(
                     contentResolver,
                     powerManager,
@@ -1183,7 +1185,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
                     flags,
                     keyguardNotificationVisibilityProvider,
                     uiEventLogger,
-                    userTracker
+                    userTracker,
+                    deviceProvisionedController
             );
             mUseHeadsUp = true;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
index c8f28bc1792655041a81332781e270839b29c2e1..4ccbd1b739f37d74df91e39c5c5b3a3a0af40c62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -65,7 +65,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase
 
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
-    private final RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy();
+    private final FakeRotationPolicy mFakeRotationPolicy = new FakeRotationPolicy();
     private DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
     private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
     private DeviceStateRotationLockSettingsManager mSettingsManager;
@@ -324,13 +324,21 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase
 
         private boolean mRotationLock;
 
-        @Override
         public void setRotationLock(boolean enabled) {
-            mRotationLock = enabled;
+            setRotationLock(enabled, /* caller= */ "FakeRotationPolicy");
         }
 
         @Override
+        public void setRotationLock(boolean enabled, String caller) {
+            mRotationLock = enabled;
+        }
+
         public void setRotationLockAtAngle(boolean enabled, int rotation) {
+            setRotationLockAtAngle(enabled, rotation, /* caller= */ "FakeRotationPolicy");
+        }
+
+        @Override
+        public void setRotationLockAtAngle(boolean enabled, int rotation, String caller) {
             mRotationLock = enabled;
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
index 94ed608f4844d0028323cbe1d5978d1ff0787629..e59e4759fc7bf8c9f7942927a5ac9d69760c7432 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
@@ -15,104 +15,119 @@
  */
 package com.android.systemui.wmshell
 
-import android.content.ContentResolver
 import android.content.Context
+import android.content.Intent
 import android.content.SharedPreferences
+import android.content.pm.ShortcutInfo
+import android.content.res.Resources
+import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.core.content.edit
 import androidx.test.filters.SmallTest
 import com.android.systemui.model.SysUiStateTest
 import com.android.wm.shell.bubbles.Bubble
 import com.android.wm.shell.bubbles.BubbleEducationController
 import com.android.wm.shell.bubbles.PREF_MANAGED_EDUCATION
 import com.android.wm.shell.bubbles.PREF_STACK_EDUCATION
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.Mockito
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class BubbleEducationControllerTest : SysUiStateTest() {
-    private val sharedPrefsEditor = Mockito.mock(SharedPreferences.Editor::class.java)
-    private val sharedPrefs = Mockito.mock(SharedPreferences::class.java)
-    private val context = Mockito.mock(Context::class.java)
+
+    private lateinit var sharedPrefs: SharedPreferences
     private lateinit var sut: BubbleEducationController
 
     @Before
     fun setUp() {
-        Mockito.`when`(context.packageName).thenReturn("packageName")
-        Mockito.`when`(context.getSharedPreferences(anyString(), anyInt())).thenReturn(sharedPrefs)
-        Mockito.`when`(context.contentResolver)
-            .thenReturn(Mockito.mock(ContentResolver::class.java))
-        Mockito.`when`(sharedPrefs.edit()).thenReturn(sharedPrefsEditor)
-        sut = BubbleEducationController(context)
+        sharedPrefs = mContext.getSharedPreferences(mContext.packageName, Context.MODE_PRIVATE)
+        sharedPrefs.edit {
+            remove(PREF_STACK_EDUCATION)
+            remove(PREF_MANAGED_EDUCATION)
+        }
+        sut = BubbleEducationController(mContext)
     }
 
     @Test
     fun testSeenStackEducation_read() {
-        Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(true)
+        sharedPrefs.edit { putBoolean(PREF_STACK_EDUCATION, true) }
         assertEquals(sut.hasSeenStackEducation, true)
-        Mockito.verify(sharedPrefs).getBoolean(PREF_STACK_EDUCATION, false)
     }
 
     @Test
     fun testSeenStackEducation_write() {
         sut.hasSeenStackEducation = true
-        Mockito.verify(sharedPrefsEditor).putBoolean(PREF_STACK_EDUCATION, true)
+        assertThat(sharedPrefs.getBoolean(PREF_STACK_EDUCATION, false)).isTrue()
     }
 
     @Test
     fun testSeenManageEducation_read() {
-        Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(true)
+        sharedPrefs.edit { putBoolean(PREF_MANAGED_EDUCATION, true) }
         assertEquals(sut.hasSeenManageEducation, true)
-        Mockito.verify(sharedPrefs).getBoolean(PREF_MANAGED_EDUCATION, false)
     }
 
     @Test
     fun testSeenManageEducation_write() {
         sut.hasSeenManageEducation = true
-        Mockito.verify(sharedPrefsEditor).putBoolean(PREF_MANAGED_EDUCATION, true)
+        assertThat(sharedPrefs.getBoolean(PREF_MANAGED_EDUCATION, false)).isTrue()
     }
 
     @Test
     fun testShouldShowStackEducation() {
-        val bubble = Mockito.mock(Bubble::class.java)
         // When bubble is null
         assertEquals(sut.shouldShowStackEducation(null), false)
+        var bubble = createFakeBubble(isConversational = false)
         // When bubble is not conversation
-        Mockito.`when`(bubble.isConversation).thenReturn(false)
         assertEquals(sut.shouldShowStackEducation(bubble), false)
         // When bubble is conversation and has seen stack edu
-        Mockito.`when`(bubble.isConversation).thenReturn(true)
-        Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(true)
+        bubble = createFakeBubble(isConversational = true)
+        sharedPrefs.edit { putBoolean(PREF_STACK_EDUCATION, true) }
         assertEquals(sut.shouldShowStackEducation(bubble), false)
         // When bubble is conversation and has not seen stack edu
-        Mockito.`when`(bubble.isConversation).thenReturn(true)
-        Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(false)
+        sharedPrefs.edit { remove(PREF_STACK_EDUCATION) }
         assertEquals(sut.shouldShowStackEducation(bubble), true)
     }
 
     @Test
     fun testShouldShowManageEducation() {
-        val bubble = Mockito.mock(Bubble::class.java)
         // When bubble is null
         assertEquals(sut.shouldShowManageEducation(null), false)
+        var bubble = createFakeBubble(isConversational = false)
         // When bubble is not conversation
-        Mockito.`when`(bubble.isConversation).thenReturn(false)
         assertEquals(sut.shouldShowManageEducation(bubble), false)
         // When bubble is conversation and has seen stack edu
-        Mockito.`when`(bubble.isConversation).thenReturn(true)
-        Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(true)
+        bubble = createFakeBubble(isConversational = true)
+        sharedPrefs.edit { putBoolean(PREF_MANAGED_EDUCATION, true) }
         assertEquals(sut.shouldShowManageEducation(bubble), false)
         // When bubble is conversation and has not seen stack edu
-        Mockito.`when`(bubble.isConversation).thenReturn(true)
-        Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(false)
+        sharedPrefs.edit { remove(PREF_MANAGED_EDUCATION) }
         assertEquals(sut.shouldShowManageEducation(bubble), true)
     }
+
+    private fun createFakeBubble(isConversational: Boolean): Bubble {
+        return if (isConversational) {
+            val shortcutInfo = ShortcutInfo.Builder(mContext, "fakeId").build()
+            Bubble(
+                "key",
+                shortcutInfo,
+                /* desiredHeight= */ 6,
+                Resources.ID_NULL,
+                "title",
+                /* taskId= */ 0,
+                "locus",
+                /* isDismissable= */ true,
+                directExecutor()
+            ) {}
+        } else {
+            val intent = Intent(Intent.ACTION_VIEW).setPackage(mContext.packageName)
+            Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index d8511e8f38e2e75c3e8ccd56ed21c3c6574eb2df..65b8b555cf2b76e9cdc745dcc27ad983f7079783 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -126,6 +126,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -391,7 +392,8 @@ public class BubblesTest extends SysuiTestCase {
                         mock(NotifPipelineFlags.class),
                         mock(KeyguardNotificationVisibilityProvider.class),
                         mock(UiEventLogger.class),
-                        mock(UserTracker.class)
+                        mock(UserTracker.class),
+                        mock(DeviceProvisionedController.class)
                 );
 
         mShellTaskOrganizer = new ShellTaskOrganizer(mock(ShellInit.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index 4e14bbf6ac1f5c8049a672727630318c33e53714..0df235dd241689038d78d49ec83357d15a4ee650 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -29,6 +29,7 @@ import com.android.systemui.statusbar.notification.interruption.KeyguardNotifica
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
 import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -48,7 +49,8 @@ public class TestableNotificationInterruptStateProviderImpl
             NotifPipelineFlags flags,
             KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
             UiEventLogger uiEventLogger,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            DeviceProvisionedController deviceProvisionedController) {
         super(contentResolver,
                 powerManager,
                 ambientDisplayConfiguration,
@@ -61,7 +63,8 @@ public class TestableNotificationInterruptStateProviderImpl
                 flags,
                 keyguardNotificationVisibilityProvider,
                 uiEventLogger,
-                userTracker);
+                userTracker,
+                deviceProvisionedController);
         mUseHeadsUp = true;
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 1620dc278950a932fa254c2faf0cfd5bfffe9d03..69c89e8f4af67325a88a8720e1658ae238bc3cec 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -206,6 +206,7 @@ class SceneTestUtils(
         return BouncerViewModel(
             applicationContext = context,
             applicationScope = applicationScope(),
+            mainDispatcher = testDispatcher,
             bouncerInteractor = bouncerInteractor,
             authenticationInteractor = authenticationInteractor,
             flags = sceneContainerFlags,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
index 4f9cb35db1a354b1716b12b68f0f8b62a18c9dce..be57658a42663867d975775707b6ae6ec2e013d4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
@@ -46,7 +46,7 @@ public class FakeRotationLockController extends BaseLeakChecker<RotationLockCont
     }
 
     @Override
-    public void setRotationLocked(boolean locked) {
+    public void setRotationLocked(boolean locked, String caller) {
 
     }
 
@@ -56,7 +56,7 @@ public class FakeRotationLockController extends BaseLeakChecker<RotationLockCont
     }
 
     @Override
-    public void setRotationLockedAtAngle(boolean locked, int rotation) {
+    public void setRotationLockedAtAngle(boolean locked, int rotation, String caller) {
 
     }
 }
diff --git a/services/Android.bp b/services/Android.bp
index f2370955f5bfaa8d2e7cb552973b7d9cde59eb32..3ae9360f3c440f7a4a368393d9c07a3eac4850cc 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -277,4 +277,5 @@ droidstubs {
             tag: ".removed-api.txt",
         },
     ],
+    api_surface: "system-server",
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 2ca84f884f780b06ed59686756f5e745b7958c96..30b9d0b59467c33305943205799225bfdc3d5acf 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -40,6 +40,7 @@ import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -166,9 +167,12 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
 
     @VisibleForTesting boolean mIsSinglePanningEnabled;
 
-    /**
-     * FullScreenMagnificationGestureHandler Constructor.
-     */
+    private final FullScreenMagnificationVibrationHelper mFullScreenMagnificationVibrationHelper;
+
+    @VisibleForTesting final OverscrollHandler mOverscrollHandler;
+
+    private final boolean mIsWatch;
+
     public FullScreenMagnificationGestureHandler(@UiContext Context context,
             FullScreenMagnificationController fullScreenMagnificationController,
             AccessibilityTraceManager trace,
@@ -254,11 +258,13 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
         mDetectingState = new DetectingState(context);
         mViewportDraggingState = new ViewportDraggingState();
         mPanningScalingState = new PanningScalingState(context);
-        mSinglePanningState = new SinglePanningState(context,
-                fullScreenMagnificationVibrationHelper);
+        mSinglePanningState = new SinglePanningState(context);
+        mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper;
         setSinglePanningEnabled(
                 context.getResources()
                         .getBoolean(R.bool.config_enable_a11y_magnification_single_panning));
+        mOverscrollHandler = new OverscrollHandler();
+        mIsWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
 
         if (mDetectShortcutTrigger) {
             mScreenStateReceiver = new ScreenStateReceiver(context, this);
@@ -468,15 +474,21 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
         @Override
         public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
             int action = event.getActionMasked();
-
             if (action == ACTION_POINTER_UP
                     && event.getPointerCount() == 2 // includes the pointer currently being released
                     && mPreviousState == mViewportDraggingState) {
-
+                // if feature flag is enabled, currently only true on watches
+                if (mIsSinglePanningEnabled) {
+                    mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
+                    mOverscrollHandler.clearEdgeState();
+                }
                 persistScaleAndTransitionTo(mViewportDraggingState);
-
             } else if (action == ACTION_UP || action == ACTION_CANCEL) {
-
+                // if feature flag is enabled, currently only true on watches
+                if (mIsSinglePanningEnabled) {
+                    mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
+                    mOverscrollHandler.clearEdgeState();
+                }
                 persistScaleAndTransitionTo(mDetectingState);
             }
         }
@@ -502,7 +514,12 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
         }
 
         public void persistScaleAndTransitionTo(State state) {
-            mFullScreenMagnificationController.persistScale(mDisplayId);
+            // If device is a watch don't change user settings scale. On watches, warp effect
+            // is enabled and the current display scale could be differ from the default user
+            // settings scale (should not change the scale due to the warp effect)
+            if (!mIsWatch) {
+                mFullScreenMagnificationController.persistScale(mDisplayId);
+            }
             clear();
             transitionTo(state);
         }
@@ -546,6 +563,9 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
             }
             mFullScreenMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
                     distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+            if (mIsSinglePanningEnabled) {
+                mOverscrollHandler.onScrollStateChanged(first, second);
+            }
             return /* event consumed: */ true;
         }
 
@@ -893,6 +913,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                         if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) {
                             transitionToViewportDraggingStateAndClear(event);
                         } else if (isActivated() && event.getPointerCount() == 2) {
+                            if (mIsSinglePanningEnabled
+                                    && overscrollState(event, mFirstPointerDownLocation)
+                                    == OVERSCROLL_VERTICAL_EDGE) {
+                                transitionToDelegatingStateAndClear();
+                            }
                             //Primary pointer is swiping, so transit to PanningScalingState
                             transitToPanningScalingStateAndClear();
                         } else if (mIsSinglePanningEnabled
@@ -1264,6 +1289,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                 + ", mMagnificationController=" + mFullScreenMagnificationController
                 + ", mDisplayId=" + mDisplayId
                 + ", mIsSinglePanningEnabled=" + mIsSinglePanningEnabled
+                + ", mOverscrollHandler=" + mOverscrollHandler
                 + '}';
     }
 
@@ -1411,32 +1437,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
 
         private final GestureDetector mScrollGestureDetector;
         private MotionEventInfo mEvent;
-        private final FullScreenMagnificationVibrationHelper
-                mFullScreenMagnificationVibrationHelper;
-
-        @VisibleForTesting int mOverscrollState;
-
-        // mPivotEdge is the point on the edge of the screen when the magnified view hits the edge
-        // This point sets the center of magnified view when warp/scale effect is triggered
-        private final PointF mPivotEdge;
-
-        // mReachedEdgeCoord is the user's pointer location on the screen when the magnified view
-        // has hit the edge
-        private final PointF mReachedEdgeCoord;
-        // mEdgeCooldown value will be set to true when user hits the edge and will be set to false
-        // once the user moves x distance away from the edge. This is so that vibrating haptic
-        // doesn't get triggered by slight movements
-        private boolean mEdgeCooldown;
-
-        SinglePanningState(
-                Context context,
-                FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) {
+        SinglePanningState(Context context) {
             mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain());
-            mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper;
-            mOverscrollState = OVERSCROLL_NONE;
-            mPivotEdge = new PointF(Float.NaN, Float.NaN);
-            mReachedEdgeCoord = new PointF(Float.NaN, Float.NaN);
-            mEdgeCooldown = false;
         }
 
         @Override
@@ -1445,17 +1447,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
             switch (action) {
                 case ACTION_UP:
                 case ACTION_CANCEL:
-                    if (mOverscrollState == OVERSCROLL_LEFT_EDGE
-                            || mOverscrollState == OVERSCROLL_RIGHT_EDGE) {
-                        mFullScreenMagnificationController.setScaleAndCenter(
-                                mDisplayId,
-                                mFullScreenMagnificationController.getPersistedScale(mDisplayId),
-                                mPivotEdge.x,
-                                mPivotEdge.y,
-                                true,
-                                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-                    }
-                    clear();
+                    mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
+                    mOverscrollHandler.clearEdgeState();
                     transitionTo(mDetectingState);
                     break;
             }
@@ -1482,23 +1475,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                                 + " isAtEdge: "
                                 + mFullScreenMagnificationController.isAtEdge(mDisplayId));
             }
-            if (mFullScreenMagnificationController.isAtEdge(mDisplayId)) {
-                playEdgeVibration(second);
-                setPivotEdge();
-            }
-            if (mOverscrollState == OVERSCROLL_NONE) {
-                mOverscrollState = overscrollState(second, new PointF(first.getX(), first.getY()));
-            } else if (mOverscrollState == OVERSCROLL_VERTICAL_EDGE) {
-                clear();
-                transitionTo(mDelegatingState);
-            } else {
-                boolean reset = warpEffectReset(second);
-                if (reset) {
-                    mFullScreenMagnificationController.reset(mDisplayId, /* animate */ true);
-                    clear();
-                    transitionTo(mDetectingState);
-                }
-            }
+            mOverscrollHandler.onScrollStateChanged(first, second);
             return /* event consumed: */ true;
         }
 
@@ -1515,35 +1492,37 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
         public String toString() {
             return "SinglePanningState{"
                     + "isEdgeOfView="
-                    + mFullScreenMagnificationController.isAtEdge(mDisplayId)
-                    + "overscrollStatus="
-                    + mOverscrollState
-                    + "}";
+                    + mFullScreenMagnificationController.isAtEdge(mDisplayId);
         }
 
-        private void playEdgeVibration(MotionEvent event) {
-            if (mOverscrollState == OVERSCROLL_NONE) {
-                vibrateIfNeeded(event);
-            }
-        }
+    }
 
-        private void setPivotEdge() {
-            if (!pointerValid(mPivotEdge)) {
-                Rect bounds = new Rect();
-                mFullScreenMagnificationController.getMagnificationBounds(mDisplayId, bounds);
-                if (mOverscrollState == OVERSCROLL_LEFT_EDGE) {
-                    mPivotEdge.set(
-                            bounds.left,
-                            mFullScreenMagnificationController.getCenterY(mDisplayId));
-                } else if (mOverscrollState == OVERSCROLL_RIGHT_EDGE) {
-                    mPivotEdge.set(
-                            bounds.right,
-                            mFullScreenMagnificationController.getCenterY(mDisplayId));
-                }
-            }
+    /** Overscroll Handler handles the logic when user is at the edge and scrolls past an edge */
+    final class OverscrollHandler {
+
+        @VisibleForTesting int mOverscrollState;
+
+        // mPivotEdge is the point on the edge of the screen when the magnified view hits the edge
+        // This point sets the center of magnified view when warp/scale effect is triggered
+        private final PointF mPivotEdge;
+
+        // mReachedEdgeCoord is the user's pointer location on the screen when the magnified view
+        // has hit the edge
+        private final PointF mReachedEdgeCoord;
+
+        // mEdgeCooldown value will be set to true when user hits the edge and will be set to false
+        // once the user moves x distance away from the edge. This is so that vibrating haptic
+        // doesn't get triggered by slight movements
+        private boolean mEdgeCooldown;
+
+        OverscrollHandler() {
+            mOverscrollState = OVERSCROLL_NONE;
+            mPivotEdge = new PointF(Float.NaN, Float.NaN);
+            mReachedEdgeCoord = new PointF(Float.NaN, Float.NaN);
+            mEdgeCooldown = false;
         }
 
-        private boolean warpEffectReset(MotionEvent second) {
+        protected boolean warpEffectReset(MotionEvent second) {
             float scale = calculateOverscrollScale(second);
             if (scale < 0) return false;
             mFullScreenMagnificationController.setScaleAndCenter(
@@ -1566,7 +1545,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
             float overshootDistX = second.getX() - mReachedEdgeCoord.x;
             if ((mOverscrollState == OVERSCROLL_LEFT_EDGE && overshootDistX < 0)
                     || (mOverscrollState == OVERSCROLL_RIGHT_EDGE && overshootDistX > 0)) {
-                clear();
+                clearEdgeState();
                 return -1.0f;
             }
             float overshootDistY = second.getY() - mReachedEdgeCoord.y;
@@ -1611,21 +1590,109 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
         }
 
         private void vibrateIfNeeded(MotionEvent event) {
+            if (mOverscrollState != OVERSCROLL_NONE) {
+                return;
+            }
             if ((mFullScreenMagnificationController.isAtLeftEdge(mDisplayId)
                             || mFullScreenMagnificationController.isAtRightEdge(mDisplayId))
                     && !mEdgeCooldown) {
                 mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled();
+            }
+        }
+
+        private void setPivotEdge(MotionEvent event) {
+            if (!pointerValid(mPivotEdge)) {
+                Rect bounds = new Rect();
+                mFullScreenMagnificationController.getMagnificationBounds(mDisplayId, bounds);
+                if (mOverscrollState == OVERSCROLL_LEFT_EDGE) {
+                    mPivotEdge.set(
+                            bounds.left, mFullScreenMagnificationController.getCenterY(mDisplayId));
+                } else if (mOverscrollState == OVERSCROLL_RIGHT_EDGE) {
+                    mPivotEdge.set(
+                            bounds.right,
+                            mFullScreenMagnificationController.getCenterY(mDisplayId));
+                }
                 mReachedEdgeCoord.set(event.getX(), event.getY());
                 mEdgeCooldown = true;
             }
         }
 
-        @Override
-        public void clear() {
+        private void onScrollStateChanged(MotionEvent first, MotionEvent second) {
+            if (mFullScreenMagnificationController.isAtEdge(mDisplayId)) {
+                vibrateIfNeeded(second);
+                setPivotEdge(second);
+            }
+            switch (mOverscrollState) {
+                case OVERSCROLL_NONE:
+                    onNoOverscroll(first, second);
+                    break;
+                case OVERSCROLL_VERTICAL_EDGE:
+                    onVerticalOverscroll();
+                    break;
+                case OVERSCROLL_LEFT_EDGE:
+                case OVERSCROLL_RIGHT_EDGE:
+                    onHorizontalOverscroll(second);
+                    break;
+                default:
+                    Slog.d(mLogTag, "Invalid overscroll state");
+                    break;
+            }
+        }
+
+        public void onNoOverscroll(MotionEvent first, MotionEvent second) {
+            mOverscrollState = overscrollState(second, new PointF(first.getX(), first.getY()));
+        }
+
+        public void onVerticalOverscroll() {
+            clearEdgeState();
+            transitionTo(mDelegatingState);
+        }
+
+        public void onHorizontalOverscroll(MotionEvent second) {
+            boolean reset = warpEffectReset(second);
+            if (reset) {
+                mFullScreenMagnificationController.reset(mDisplayId, /* animate */ true);
+                clearEdgeState();
+                transitionTo(mDelegatingState);
+            }
+        }
+
+        private void setScaleAndCenterToEdgeIfNeeded() {
+            if (mOverscrollState == OVERSCROLL_LEFT_EDGE
+                    || mOverscrollState == OVERSCROLL_RIGHT_EDGE) {
+                mFullScreenMagnificationController.setScaleAndCenter(
+                        mDisplayId,
+                        mFullScreenMagnificationController.getPersistedScale(mDisplayId),
+                        mPivotEdge.x,
+                        mPivotEdge.y,
+                        true,
+                        AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+            }
+        }
+
+        private void clearEdgeState() {
             mOverscrollState = OVERSCROLL_NONE;
             mPivotEdge.set(Float.NaN, Float.NaN);
             mReachedEdgeCoord.set(Float.NaN, Float.NaN);
             mEdgeCooldown = false;
         }
+
+        @Override
+        public String toString() {
+            return "OverscrollHandler {"
+                    + "mOverscrollState="
+                    + mOverscrollState
+                    + "mPivotEdge.x="
+                    + mPivotEdge.x
+                    + "mPivotEdge.y="
+                    + mPivotEdge.y
+                    + "mReachedEdgeCoord.x="
+                    + mReachedEdgeCoord.x
+                    + "mReachedEdgeCoord.y="
+                    + mReachedEdgeCoord.y
+                    + "mEdgeCooldown="
+                    + mEdgeCooldown
+                    + "}";
+        }
     }
 }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index ba4533960db4e22e616c6286949d6518ffb39de8..c79149816b1ac8300935f6798b4b058dfc07760a 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -155,6 +155,7 @@ public class CompanionDeviceManagerService extends SystemService {
             "debug.cdm.cdmservice.removal_time_window";
 
     private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
+    private static final int MAX_CN_LENGTH = 500;
 
     private final ActivityManager mActivityManager;
     private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
@@ -757,6 +758,9 @@ public class CompanionDeviceManagerService extends SystemService {
             String callingPackage = component.getPackageName();
             checkCanCallNotificationApi(callingPackage);
             // TODO: check userId.
+            if (component.flattenToString().length() > MAX_CN_LENGTH) {
+                throw new IllegalArgumentException("Component name is too long.");
+            }
             final long identity = Binder.clearCallingIdentity();
             try {
                 return PendingIntent.getActivityAsUser(getContext(),
diff --git a/services/core/Android.bp b/services/core/Android.bp
index d9c269410b9367665bcd504dd316ffde0c960c4e..6521fabe5b7c0de75cc31932b3cada21d09a8351 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -194,6 +194,7 @@ java_library_static {
         "notification_flags_lib",
         "camera_platform_flags_core_java_lib",
         "biometrics_flags_lib",
+        "am_flags_lib",
     ],
     javac_shard_size: 50,
     javacflags: [
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index c20f0aa4a62a94d3ffc991d215e5767a21c5fc48..9716cf69015cf1f97881b4ea551b50b9ebcdc68a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -225,7 +225,7 @@ final class ActivityManagerConstants extends ContentObserver {
     /**
      * The default value to {@link #KEY_ENABLE_NEW_OOMADJ}.
      */
-    private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = false;
+    private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = Flags.oomadjusterCorrectnessRewrite();
 
     /**
      * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
diff --git a/services/core/java/com/android/server/am/Android.bp b/services/core/java/com/android/server/am/Android.bp
new file mode 100644
index 0000000000000000000000000000000000000000..af1200e4bdf8177798cb9e1727919a5357c33299
--- /dev/null
+++ b/services/core/java/com/android/server/am/Android.bp
@@ -0,0 +1,10 @@
+aconfig_declarations {
+    name: "am_flags",
+    package: "com.android.server.am",
+    srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "am_flags_lib",
+    aconfig_declarations: "am_flags",
+}
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
new file mode 100644
index 0000000000000000000000000000000000000000..b03cc6295b8d5046db2c894535b037c7700af338
--- /dev/null
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.server.am"
+
+flag {
+    name: "oomadjuster_correctness_rewrite"
+    namespace: "android_platform_power_optimization"
+    description: "Utilize new OomAdjuster implementation"
+    bug: "298055811"
+    is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 507ae2676b16ab2d1247e18483ca6c2f82c42aee..9e92c8d7342db16a4009e36f02a93648d81bd833 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -500,6 +500,8 @@ public class DisplayDeviceConfig {
 
     public static final String DEFAULT_ID = "default";
 
+    public static final int DEFAULT_LOW_REFRESH_RATE = 60;
+
     private static final float BRIGHTNESS_DEFAULT = 0.5f;
     private static final String ETC_DIR = "etc";
     private static final String DISPLAY_CONFIG_DIR = "displayconfig";
@@ -513,7 +515,6 @@ public class DisplayDeviceConfig {
     private static final int DEFAULT_PEAK_REFRESH_RATE = 0;
     private static final int DEFAULT_REFRESH_RATE = 60;
     private static final int DEFAULT_REFRESH_RATE_IN_HBM = 0;
-    private static final int DEFAULT_LOW_REFRESH_RATE = 60;
     private static final int DEFAULT_HIGH_REFRESH_RATE = 0;
     private static final float[] DEFAULT_BRIGHTNESS_THRESHOLDS = new float[]{};
 
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 213ee646fc34f49b7201f7c93eb7889d24abf5f1..3529b048bd348c1682cdfb9ebd750519de12e108 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import static android.view.Display.Mode.INVALID_MODE_ID;
+
 import android.hardware.display.DeviceProductInfo;
 import android.hardware.display.DisplayViewport;
 import android.util.DisplayMetrics;
@@ -274,6 +276,11 @@ final class DisplayDeviceInfo {
      */
     public int defaultModeId;
 
+    /**
+     * The mode of the display which is preferred by user.
+     */
+    public int userPreferredModeId = INVALID_MODE_ID;
+
     /**
      * The supported modes of the display.
      */
@@ -472,6 +479,7 @@ final class DisplayDeviceInfo {
                 || modeId != other.modeId
                 || renderFrameRate != other.renderFrameRate
                 || defaultModeId != other.defaultModeId
+                || userPreferredModeId != other.userPreferredModeId
                 || !Arrays.equals(supportedModes, other.supportedModes)
                 || !Arrays.equals(supportedColorModes, other.supportedColorModes)
                 || !Objects.equals(hdrCapabilities, other.hdrCapabilities)
@@ -517,6 +525,7 @@ final class DisplayDeviceInfo {
         modeId = other.modeId;
         renderFrameRate = other.renderFrameRate;
         defaultModeId = other.defaultModeId;
+        userPreferredModeId = other.userPreferredModeId;
         supportedModes = other.supportedModes;
         colorMode = other.colorMode;
         supportedColorModes = other.supportedColorModes;
@@ -559,6 +568,7 @@ final class DisplayDeviceInfo {
         sb.append(", modeId ").append(modeId);
         sb.append(", renderFrameRate ").append(renderFrameRate);
         sb.append(", defaultModeId ").append(defaultModeId);
+        sb.append(", userPreferredModeId ").append(userPreferredModeId);
         sb.append(", supportedModes ").append(Arrays.toString(supportedModes));
         sb.append(", colorMode ").append(colorMode);
         sb.append(", supportedColorModes ").append(Arrays.toString(supportedColorModes));
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 46ef6c3bd3e46acc39ba9effa2255ce90ca8541f..9ef84cb8417e7230a91c45b258764f151e62463a 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -557,7 +557,7 @@ public final class DisplayManagerService extends SystemService {
         mLogicalDisplayMapper = new LogicalDisplayMapper(mContext,
                 new FoldSettingProvider(mContext, new SettingsWrapper()), mDisplayDeviceRepo,
                 new LogicalDisplayListener(), mSyncRoot, mHandler, mFlags);
-        mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
+        mDisplayModeDirector = new DisplayModeDirector(context, mHandler, mFlags);
         mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
         Resources resources = mContext.getResources();
         mDefaultDisplayDefaultColorMode = mContext.getResources().getInteger(
@@ -2028,9 +2028,6 @@ public final class DisplayManagerService extends SystemService {
         mDisplayBrightnesses.delete(displayId);
         DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
 
-        sendDisplayEventLocked(display, event);
-        scheduleTraversalLocked(false);
-
         if (mDisplayWindowPolicyControllers.contains(displayId)) {
             final IVirtualDevice virtualDevice =
                     mDisplayWindowPolicyControllers.removeReturnOld(displayId).first;
@@ -2041,6 +2038,9 @@ public final class DisplayManagerService extends SystemService {
                 });
             }
         }
+
+        sendDisplayEventLocked(display, event);
+        scheduleTraversalLocked(false);
     }
 
     private void handleLogicalDisplaySwappedLocked(@NonNull LogicalDisplay display) {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 924b1b3c66abefd96b3b2e38edb59d325ef8858e..0f3e7c5196ca23c1c5da8188b3c689f982bc3031 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -640,6 +640,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
                 mInfo.modeId = mActiveModeId;
                 mInfo.renderFrameRate = mActiveRenderFrameRate;
                 mInfo.defaultModeId = getPreferredModeId();
+                mInfo.userPreferredModeId = mUserPreferredModeId;
                 mInfo.supportedModes = getDisplayModes(mSupportedModes);
                 mInfo.colorMode = mActiveColorMode;
                 mInfo.allmSupported = mAllmSupported;
@@ -1344,6 +1345,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
 
     public interface DisplayEventListener {
         void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected);
+        void onHotplugConnectionError(long timestampNanos, int connectionError);
         void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
                 long renderPeriod);
         void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
@@ -1365,6 +1367,11 @@ final class LocalDisplayAdapter extends DisplayAdapter {
             mListener.onHotplug(timestampNanos, physicalDisplayId, connected);
         }
 
+        @Override
+        public void onHotplugConnectionError(long timestampNanos, int errorCode) {
+            mListener.onHotplugConnectionError(timestampNanos, errorCode);
+        }
+
         @Override
         public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
                 long renderPeriod) {
@@ -1390,6 +1397,15 @@ final class LocalDisplayAdapter extends DisplayAdapter {
             }
         }
 
+        @Override
+        public void onHotplugConnectionError(long timestampNanos, int connectionError) {
+            if (DEBUG) {
+                Slog.d(TAG, "onHotplugConnectionError("
+                        + "timestampNanos=" + timestampNanos
+                        + ", connectionError=" + connectionError + ")");
+            }
+        }
+
         @Override
         public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
                 long renderPeriod) {
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 0405ebe8b5d2e015e9b0daebd50997d8aec197c5..d4d104e862f0d20da991d77187d34ad1ab4c627c 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -470,6 +470,7 @@ final class LogicalDisplay {
             mBaseDisplayInfo.modeId = deviceInfo.modeId;
             mBaseDisplayInfo.renderFrameRate = deviceInfo.renderFrameRate;
             mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId;
+            mBaseDisplayInfo.userPreferredModeId = deviceInfo.userPreferredModeId;
             mBaseDisplayInfo.supportedModes = Arrays.copyOf(
                     deviceInfo.supportedModes, deviceInfo.supportedModes.length);
             mBaseDisplayInfo.colorMode = deviceInfo.colorMode;
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 3f6bf1adfe48e1bd555a4828b335a0a7b8b8d6dc..ff768d64a7e11c16ee6e0415d8a6cca51fcdc6c7 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -47,6 +47,22 @@ public class DisplayManagerFlags {
             Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1,
             Flags::enableAdaptiveToneImprovements1);
 
+    private final FlagState mDisplayResolutionRangeVotingState = new FlagState(
+            Flags.FLAG_ENABLE_DISPLAY_RESOLUTION_RANGE_VOTING,
+            Flags::enableDisplayResolutionRangeVoting);
+
+    private final FlagState mUserPreferredModeVoteState = new FlagState(
+            Flags.FLAG_ENABLE_USER_PREFERRED_MODE_VOTE,
+            Flags::enableUserPreferredModeVote);
+
+    private final FlagState mExternalDisplayLimitModeState = new FlagState(
+            Flags.FLAG_ENABLE_MODE_LIMIT_FOR_EXTERNAL_DISPLAY,
+            Flags::enableModeLimitForExternalDisplay);
+
+    private final FlagState mDisplaysRefreshRatesSynchronizationState = new FlagState(
+            Flags.FLAG_ENABLE_DISPLAYS_REFRESH_RATES_SYNCHRONIZATION,
+            Flags::enableDisplaysRefreshRatesSynchronization);
+
     /** Returns whether connected display management is enabled or not. */
     public boolean isConnectedDisplayManagementEnabled() {
         return mConnectedDisplayManagementFlagState.isEnabled();
@@ -68,6 +84,33 @@ public class DisplayManagerFlags {
         return mAdaptiveToneImprovements1.isEnabled();
     }
 
+    /** Returns whether resolution range voting feature is enabled or not. */
+    public boolean isDisplayResolutionRangeVotingEnabled() {
+        return mDisplayResolutionRangeVotingState.isEnabled();
+    }
+
+    /**
+     * @return Whether user preferred mode is added as a vote in
+     *      {@link com.android.server.display.mode.DisplayModeDirector}
+     */
+    public boolean isUserPreferredModeVoteEnabled() {
+        return mUserPreferredModeVoteState.isEnabled();
+    }
+
+    /**
+     * @return Whether external display mode limitation is enabled.
+     */
+    public boolean isExternalDisplayLimitModeEnabled() {
+        return mExternalDisplayLimitModeState.isEnabled();
+    }
+
+    /**
+     * @return Whether displays refresh rate synchronization is enabled.
+     */
+    public boolean isDisplaysRefreshRatesSynchronizationEnabled() {
+        return mDisplaysRefreshRatesSynchronizationState.isEnabled();
+    }
+
     private static class FlagState {
 
         private final String mName;
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 4d8600448c33989ed064bc832407e572f2fa28a5..a5b8cbbcdec4b1de297921b16daa87ce12ed4ab6 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -5,7 +5,7 @@ package: "com.android.server.display.feature.flags"
 flag {
     name: "enable_connected_display_management"
     namespace: "display_manager"
-    description: "Feature flag for Connected Display managment"
+    description: "Feature flag for Connected Display management"
     bug: "280739508"
     is_fixed_read_only: true
 }
@@ -34,3 +34,34 @@ flag {
     is_fixed_read_only: true
 }
 
+flag {
+    name: "enable_display_resolution_range_voting"
+    namespace: "display_manager"
+    description: "Feature flag to enable voting for ranges of resolutions"
+    bug: "299297058"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "enable_user_preferred_mode_vote"
+    namespace: "display_manager"
+    description: "Feature flag to use voting for UserPreferredMode for display"
+    bug: "297018612"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "enable_mode_limit_for_external_display"
+    namespace: "display_manager"
+    description: "Feature limiting external display resolution and refresh rate"
+    bug: "242093547"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "enable_displays_refresh_rates_synchronization"
+    namespace: "display_manager"
+    description: "Enables synchronization of refresh rates across displays"
+    bug: "294015845"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 2c2af3f7f435322d219dd8d60019e04332034b28..71ea8cc30405881cbeca8f9f49292afedafcc24f 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -19,6 +19,9 @@ package com.android.server.display.mode;
 import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
 import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE;
 import static android.os.PowerManager.BRIGHTNESS_INVALID_FLOAT;
+import static android.view.Display.Mode.INVALID_MODE_ID;
+
+import static com.android.server.display.DisplayDeviceConfig.DEFAULT_LOW_REFRESH_RATE;
 
 import android.annotation.IntegerRes;
 import android.annotation.NonNull;
@@ -71,6 +74,7 @@ import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.display.DisplayDeviceConfig;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.utils.AmbientFilter;
 import com.android.server.display.utils.AmbientFilterFactory;
 import com.android.server.display.utils.DeviceConfigParsingUtils;
@@ -84,9 +88,11 @@ import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.function.Function;
 import java.util.function.IntSupplier;
@@ -96,6 +102,8 @@ import java.util.function.IntSupplier;
  * picked by the system based on system-wide and display-specific configuration.
  */
 public class DisplayModeDirector {
+    public static final float SYNCHRONIZED_REFRESH_RATE_TARGET = DEFAULT_LOW_REFRESH_RATE;
+    public static final float SYNCHRONIZED_REFRESH_RATE_TOLERANCE = 1;
     private static final String TAG = "DisplayModeDirector";
     private boolean mLoggingEnabled;
 
@@ -151,12 +159,38 @@ public class DisplayModeDirector {
     @DisplayManager.SwitchingType
     private int mModeSwitchingType = DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS;
 
-    public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler) {
-        this(context, handler, new RealInjector(context));
+    /**
+     * Whether resolution range voting feature is enabled.
+     */
+    private final boolean mIsDisplayResolutionRangeVotingEnabled;
+
+    /**
+     * Whether user preferred mode voting feature is enabled.
+     */
+    private final boolean mIsUserPreferredModeVoteEnabled;
+
+    /**
+     * Whether limit display mode feature is enabled.
+     */
+    private final boolean mIsExternalDisplayLimitModeEnabled;
+
+    private final boolean mIsDisplaysRefreshRatesSynchronizationEnabled;
+
+    public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler,
+            @NonNull DisplayManagerFlags displayManagerFlags) {
+        this(context, handler, new RealInjector(context), displayManagerFlags);
     }
 
     public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler,
-            @NonNull Injector injector) {
+            @NonNull Injector injector,
+            @NonNull DisplayManagerFlags displayManagerFlags) {
+        mIsDisplayResolutionRangeVotingEnabled = displayManagerFlags
+                .isDisplayResolutionRangeVotingEnabled();
+        mIsUserPreferredModeVoteEnabled = displayManagerFlags.isUserPreferredModeVoteEnabled();
+        mIsExternalDisplayLimitModeEnabled = displayManagerFlags
+            .isExternalDisplayLimitModeEnabled();
+        mIsDisplaysRefreshRatesSynchronizationEnabled = displayManagerFlags
+            .isDisplaysRefreshRatesSynchronizationEnabled();
         mContext = context;
         mHandler = new DisplayModeDirectorHandler(handler.getLooper());
         mInjector = injector;
@@ -230,6 +264,8 @@ public class DisplayModeDirector {
         public float maxRenderFrameRate;
         public int width;
         public int height;
+        public int minWidth;
+        public int minHeight;
         public boolean disableRefreshRateSwitching;
         public float appRequestBaseModeRefreshRate;
 
@@ -244,6 +280,8 @@ public class DisplayModeDirector {
             maxRenderFrameRate = Float.POSITIVE_INFINITY;
             width = Vote.INVALID_SIZE;
             height = Vote.INVALID_SIZE;
+            minWidth = 0;
+            minHeight = 0;
             disableRefreshRateSwitching = false;
             appRequestBaseModeRefreshRate = 0f;
         }
@@ -256,6 +294,8 @@ public class DisplayModeDirector {
                     + ", maxRenderFrameRate=" + maxRenderFrameRate
                     + ", width=" + width
                     + ", height=" + height
+                    + ", minWidth=" + minWidth
+                    + ", minHeight=" + minHeight
                     + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching
                     + ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate;
         }
@@ -277,7 +317,6 @@ public class DisplayModeDirector {
                 continue;
             }
 
-
             // For physical refresh rates, just use the tightest bounds of all the votes.
             // The refresh rate cannot be lower than the minimal render frame rate.
             final float minPhysicalRefreshRate = Math.max(vote.refreshRateRanges.physical.min,
@@ -298,10 +337,18 @@ public class DisplayModeDirector {
             // For display size, disable refresh rate switching and base mode refresh rate use only
             // the first vote we come across (i.e. the highest priority vote that includes the
             // attribute).
-            if (summary.height == Vote.INVALID_SIZE && summary.width == Vote.INVALID_SIZE
-                    && vote.height > 0 && vote.width > 0) {
-                summary.width = vote.width;
-                summary.height = vote.height;
+            if (vote.height > 0 && vote.width > 0) {
+                if (summary.width == Vote.INVALID_SIZE && summary.height == Vote.INVALID_SIZE) {
+                    summary.width = vote.width;
+                    summary.height = vote.height;
+                    summary.minWidth = vote.minWidth;
+                    summary.minHeight = vote.minHeight;
+                } else if (mIsDisplayResolutionRangeVotingEnabled) {
+                    summary.width = Math.min(summary.width, vote.width);
+                    summary.height = Math.min(summary.height, vote.height);
+                    summary.minWidth = Math.max(summary.minWidth, vote.minWidth);
+                    summary.minHeight = Math.max(summary.minHeight, vote.minHeight);
+                }
             }
             if (!summary.disableRefreshRateSwitching && vote.disableRefreshRateSwitching) {
                 summary.disableRefreshRateSwitching = true;
@@ -413,6 +460,8 @@ public class DisplayModeDirector {
                         || primarySummary.width == Vote.INVALID_SIZE) {
                     primarySummary.width = defaultMode.getPhysicalWidth();
                     primarySummary.height = defaultMode.getPhysicalHeight();
+                } else if (mIsDisplayResolutionRangeVotingEnabled) {
+                    updateSummaryWithBestAllowedResolution(modes, primarySummary);
                 }
 
                 availableModes = filterModes(modes, primarySummary);
@@ -654,6 +703,38 @@ public class DisplayModeDirector {
         return availableModes;
     }
 
+    private void updateSummaryWithBestAllowedResolution(final Display.Mode[] supportedModes,
+            VoteSummary outSummary) {
+        final int maxAllowedWidth = outSummary.width;
+        final int maxAllowedHeight = outSummary.height;
+        if (mLoggingEnabled) {
+            Slog.i(TAG, "updateSummaryWithBestAllowedResolution " + outSummary);
+        }
+        outSummary.width = Vote.INVALID_SIZE;
+        outSummary.height = Vote.INVALID_SIZE;
+
+        int maxNumberOfPixels = 0;
+        for (Display.Mode mode : supportedModes) {
+            if (mode.getPhysicalWidth() > maxAllowedWidth
+                    || mode.getPhysicalHeight() > maxAllowedHeight
+                    || mode.getPhysicalWidth() < outSummary.minWidth
+                    || mode.getPhysicalHeight() < outSummary.minHeight) {
+                continue;
+            }
+
+            int numberOfPixels = mode.getPhysicalHeight() * mode.getPhysicalWidth();
+            if (numberOfPixels > maxNumberOfPixels || (mode.getPhysicalWidth() == maxAllowedWidth
+                    && mode.getPhysicalHeight() == maxAllowedHeight)) {
+                if (mLoggingEnabled) {
+                    Slog.i(TAG, "updateSummaryWithBestAllowedResolution updated with " + mode);
+                }
+                maxNumberOfPixels = numberOfPixels;
+                outSummary.width = mode.getPhysicalWidth();
+                outSummary.height = mode.getPhysicalHeight();
+            }
+        }
+    }
+
     /**
      * Gets the observer responsible for application display mode requests.
      */
@@ -1393,11 +1474,38 @@ public class DisplayModeDirector {
         private final Context mContext;
         private final Handler mHandler;
         private final VotesStorage mVotesStorage;
+        private int mExternalDisplayPeakWidth;
+        private int mExternalDisplayPeakHeight;
+        private int mExternalDisplayPeakRefreshRate;
+        private final boolean mRefreshRateSynchronizationEnabled;
+        private final Set<Integer> mExternalDisplaysConnected = new HashSet<>();
 
         DisplayObserver(Context context, Handler handler, VotesStorage votesStorage) {
             mContext = context;
             mHandler = handler;
             mVotesStorage = votesStorage;
+            mExternalDisplayPeakRefreshRate = mContext.getResources().getInteger(
+                        R.integer.config_externalDisplayPeakRefreshRate);
+            mExternalDisplayPeakWidth = mContext.getResources().getInteger(
+                        R.integer.config_externalDisplayPeakWidth);
+            mExternalDisplayPeakHeight = mContext.getResources().getInteger(
+                        R.integer.config_externalDisplayPeakHeight);
+            mRefreshRateSynchronizationEnabled = mContext.getResources().getBoolean(
+                        R.bool.config_refreshRateSynchronizationEnabled);
+        }
+
+        private boolean isExternalDisplayLimitModeEnabled() {
+            return mExternalDisplayPeakWidth > 0
+                && mExternalDisplayPeakHeight > 0
+                && mExternalDisplayPeakRefreshRate > 0
+                && mIsExternalDisplayLimitModeEnabled
+                && mIsDisplayResolutionRangeVotingEnabled
+                && mIsUserPreferredModeVoteEnabled;
+        }
+
+        private boolean isRefreshRateSynchronizationEnabled() {
+            return mRefreshRateSynchronizationEnabled
+                && mIsDisplaysRefreshRatesSynchronizationEnabled;
         }
 
         public void observe() {
@@ -1428,6 +1536,9 @@ public class DisplayModeDirector {
             DisplayInfo displayInfo = getDisplayInfo(displayId);
             updateDisplayModes(displayId, displayInfo);
             updateLayoutLimitedFrameRate(displayId, displayInfo);
+            updateUserSettingDisplayPreferredSize(displayInfo);
+            updateDisplaysPeakRefreshRateAndResolution(displayInfo);
+            addDisplaysSynchronizedPeakRefreshRate(displayInfo);
         }
 
         @Override
@@ -1437,6 +1548,9 @@ public class DisplayModeDirector {
                 mDefaultModeByDisplay.remove(displayId);
             }
             updateLayoutLimitedFrameRate(displayId, null);
+            removeUserSettingDisplayPreferredSize(displayId);
+            removeDisplaysPeakRefreshRateAndResolution(displayId);
+            removeDisplaysSynchronizedPeakRefreshRate(displayId);
         }
 
         @Override
@@ -1444,6 +1558,7 @@ public class DisplayModeDirector {
             DisplayInfo displayInfo = getDisplayInfo(displayId);
             updateDisplayModes(displayId, displayInfo);
             updateLayoutLimitedFrameRate(displayId, displayInfo);
+            updateUserSettingDisplayPreferredSize(displayInfo);
         }
 
         @Nullable
@@ -1460,6 +1575,111 @@ public class DisplayModeDirector {
             mVotesStorage.updateVote(displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, vote);
         }
 
+        private void removeUserSettingDisplayPreferredSize(int displayId) {
+            if (!mIsUserPreferredModeVoteEnabled) {
+                return;
+            }
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE,
+                    null);
+        }
+
+        private void updateUserSettingDisplayPreferredSize(@Nullable DisplayInfo info) {
+            if (info == null || !mIsUserPreferredModeVoteEnabled) {
+                return;
+            }
+
+            var preferredMode = findDisplayPreferredMode(info);
+            if (preferredMode == null) {
+                removeUserSettingDisplayPreferredSize(info.displayId);
+                return;
+            }
+
+            mVotesStorage.updateVote(info.displayId,
+                    Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE,
+                    Vote.forSize(/* width */ preferredMode.getPhysicalWidth(),
+                            /* height */ preferredMode.getPhysicalHeight()));
+        }
+
+        @Nullable
+        private Display.Mode findDisplayPreferredMode(@NonNull DisplayInfo info) {
+            if (info.userPreferredModeId == INVALID_MODE_ID) {
+                return null;
+            }
+            for (var mode : info.supportedModes) {
+                if (mode.getModeId() == info.userPreferredModeId) {
+                    return mode;
+                }
+            }
+            return null;
+        }
+
+        private void removeDisplaysPeakRefreshRateAndResolution(int displayId) {
+            if (!isExternalDisplayLimitModeEnabled()) {
+                return;
+            }
+
+            mVotesStorage.updateVote(displayId,
+                    Vote.PRIORITY_LIMIT_MODE, null);
+        }
+
+        private void updateDisplaysPeakRefreshRateAndResolution(@Nullable final DisplayInfo info) {
+            // Only consider external display, only in case the refresh rate and resolution limits
+            // are non-zero.
+            if (info == null || info.type != Display.TYPE_EXTERNAL
+                    || !isExternalDisplayLimitModeEnabled()) {
+                return;
+            }
+
+            mVotesStorage.updateVote(info.displayId,
+                    Vote.PRIORITY_LIMIT_MODE,
+                    Vote.forSizeAndPhysicalRefreshRatesRange(
+                            /* minWidth */ 0, /* minHeight */ 0,
+                            mExternalDisplayPeakWidth,
+                            mExternalDisplayPeakHeight,
+                            /* minPhysicalRefreshRate */ 0,
+                            mExternalDisplayPeakRefreshRate));
+        }
+
+        /**
+         * Sets 60Hz target refresh rate as the vote with
+         * {@link Vote#PRIORITY_SYNCHRONIZED_REFRESH_RATE} priority.
+         */
+        private void addDisplaysSynchronizedPeakRefreshRate(@Nullable final DisplayInfo info) {
+            if (info == null || info.type != Display.TYPE_EXTERNAL
+                    || !isRefreshRateSynchronizationEnabled()) {
+                return;
+            }
+            synchronized (mLock) {
+                mExternalDisplaysConnected.add(info.displayId);
+                if (mExternalDisplaysConnected.size() != 1) {
+                    return;
+                }
+            }
+            // set minRefreshRate as the max refresh rate.
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE,
+                    Vote.forPhysicalRefreshRates(
+                            SYNCHRONIZED_REFRESH_RATE_TARGET
+                                - SYNCHRONIZED_REFRESH_RATE_TOLERANCE,
+                            SYNCHRONIZED_REFRESH_RATE_TARGET
+                                + SYNCHRONIZED_REFRESH_RATE_TOLERANCE));
+        }
+
+        private void removeDisplaysSynchronizedPeakRefreshRate(final int displayId) {
+            if (!isRefreshRateSynchronizationEnabled()) {
+                return;
+            }
+            synchronized (mLock) {
+                if (!mExternalDisplaysConnected.contains(displayId)) {
+                    return;
+                }
+                mExternalDisplaysConnected.remove(displayId);
+                if (mExternalDisplaysConnected.size() != 0) {
+                    return;
+                }
+            }
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE, null);
+        }
+
         private void updateDisplayModes(int displayId, @Nullable DisplayInfo info) {
             if (info == null) {
                 return;
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index a42d8f257ddfef21711f654878c2ad79a63c3e4a..b6a6069b5a63acf9199c0fbe8468250df9405187 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -18,6 +18,8 @@ package com.android.server.display.mode;
 
 import android.view.SurfaceControl;
 
+import java.util.Objects;
+
 final class Vote {
     // DEFAULT_RENDER_FRAME_RATE votes for render frame rate [0, DEFAULT]. As the lowest
     // priority vote, it's overridden by all other considerations. It acts to set a default
@@ -36,12 +38,15 @@ final class Vote {
     // It votes [minRefreshRate, Float.POSITIVE_INFINITY]
     static final int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3;
 
+    // User setting preferred display resolution.
+    static final int PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE = 4;
+
     // APP_REQUEST_RENDER_FRAME_RATE_RANGE is used to for internal apps to limit the render
     // frame rate in certain cases, mostly to preserve power.
     // @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate
     // @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate
     // It votes to [preferredMinRefreshRate, preferredMaxRefreshRate].
-    static final int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 4;
+    static final int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 5;
 
     // We split the app request into different priorities in case we can satisfy one desire
     // without the other.
@@ -67,40 +72,47 @@ final class Vote {
     // The preferred refresh rate is set on the main surface of the app outside of
     // DisplayModeDirector.
     // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded
-    static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 5;
-    static final int PRIORITY_APP_REQUEST_SIZE = 6;
+    static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 6;
+
+    static final int PRIORITY_APP_REQUEST_SIZE = 7;
 
     // SETTING_PEAK_RENDER_FRAME_RATE has a high priority and will restrict the bounds of the
     // rest of low priority voters. It votes [0, max(PEAK, MIN)]
-    static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 7;
+    static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 8;
+
+    // Restrict all displays to 60Hz when external display is connected. It votes [59Hz, 61Hz].
+    static final int PRIORITY_SYNCHRONIZED_REFRESH_RATE = 9;
+
+    // Restrict displays max available resolution and refresh rates. It votes [0, LIMIT]
+    static final int PRIORITY_LIMIT_MODE = 10;
 
     // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh
     // rate to max value (same as for PRIORITY_UDFPS) on lock screen
-    static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 8;
+    static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 11;
 
     // For concurrent displays we want to limit refresh rate on all displays
-    static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 9;
+    static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 12;
 
     // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if
     // Settings.Global.LOW_POWER_MODE is on.
-    static final int PRIORITY_LOW_POWER_MODE = 10;
+    static final int PRIORITY_LOW_POWER_MODE = 13;
 
     // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
     // higher priority voters' result is a range, it will fix the rate to a single choice.
     // It's used to avoid refresh rate switches in certain conditions which may result in the
     // user seeing the display flickering when the switches occur.
-    static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 11;
+    static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 14;
 
     // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
-    static final int PRIORITY_SKIN_TEMPERATURE = 12;
+    static final int PRIORITY_SKIN_TEMPERATURE = 15;
 
     // The proximity sensor needs the refresh rate to be locked in order to function, so this is
     // set to a high priority.
-    static final int PRIORITY_PROXIMITY = 13;
+    static final int PRIORITY_PROXIMITY = 16;
 
     // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
     // to function, so this needs to be the highest priority of all votes.
-    static final int PRIORITY_UDFPS = 14;
+    static final int PRIORITY_UDFPS = 17;
 
     // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
     // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
@@ -126,6 +138,14 @@ final class Vote {
      * The requested height of the display in pixels, or INVALID_SIZE;
      */
     public final int height;
+    /**
+     * Min requested width of the display in pixels, or 0;
+     */
+    public final int minWidth;
+    /**
+     * Min requested height of the display in pixels, or 0;
+     */
+    public final int minHeight;
     /**
      * Information about the refresh rate frame rate ranges DM would like to set the display to.
      */
@@ -144,42 +164,82 @@ final class Vote {
     public final float appRequestBaseModeRefreshRate;
 
     static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) {
-        return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate, 0,
-                Float.POSITIVE_INFINITY,
-                minRefreshRate == maxRefreshRate, 0f);
+        return new Vote(/* minWidth= */ 0, /* minHeight= */ 0,
+                /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE,
+                /* minPhysicalRefreshRate= */ minRefreshRate,
+                /* maxPhysicalRefreshRate= */ maxRefreshRate,
+                /* minRenderFrameRate= */ 0,
+                /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY,
+                /* disableRefreshRateSwitching= */ minRefreshRate == maxRefreshRate,
+                /* baseModeRefreshRate= */ 0f);
     }
 
     static Vote forRenderFrameRates(float minFrameRate, float maxFrameRate) {
-        return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, minFrameRate,
+        return new Vote(/* minWidth= */ 0, /* minHeight= */ 0,
+                /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE,
+                /* minPhysicalRefreshRate= */ 0,
+                /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY,
+                minFrameRate,
                 maxFrameRate,
-                false, 0f);
+                /* disableRefreshRateSwitching= */ false,
+                /* baseModeRefreshRate= */ 0f);
     }
 
     static Vote forSize(int width, int height) {
-        return new Vote(width, height, 0, Float.POSITIVE_INFINITY, 0, Float.POSITIVE_INFINITY,
-                false,
-                0f);
+        return new Vote(/* minWidth= */ width, /* minHeight= */ height,
+                width, height,
+                /* minPhysicalRefreshRate= */ 0,
+                /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY,
+                /* minRenderFrameRate= */ 0,
+                /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY,
+                /* disableRefreshRateSwitching= */ false,
+                /* baseModeRefreshRate= */ 0f);
+    }
+
+    static Vote forSizeAndPhysicalRefreshRatesRange(int minWidth, int minHeight,
+            int width, int height, float minRefreshRate, float maxRefreshRate) {
+        return new Vote(minWidth, minHeight,
+                width, height,
+                minRefreshRate,
+                maxRefreshRate,
+                /* minRenderFrameRate= */ 0,
+                /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY,
+                /* disableRefreshRateSwitching= */ minRefreshRate == maxRefreshRate,
+                /* baseModeRefreshRate= */ 0f);
     }
 
     static Vote forDisableRefreshRateSwitching() {
-        return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0,
-                Float.POSITIVE_INFINITY, true,
-                0f);
+        return new Vote(/* minWidth= */ 0, /* minHeight= */ 0,
+                /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE,
+                /* minPhysicalRefreshRate= */ 0,
+                /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY,
+                /* minRenderFrameRate= */ 0,
+                /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY,
+                /* disableRefreshRateSwitching= */ true,
+                /* baseModeRefreshRate= */ 0f);
     }
 
     static Vote forBaseModeRefreshRate(float baseModeRefreshRate) {
-        return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0,
-                Float.POSITIVE_INFINITY, false,
-                baseModeRefreshRate);
+        return new Vote(/* minWidth= */ 0, /* minHeight= */ 0,
+                /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE,
+                /* minPhysicalRefreshRate= */ 0,
+                /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY,
+                /* minRenderFrameRate= */ 0,
+                /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY,
+                /* disableRefreshRateSwitching= */ false,
+                /* baseModeRefreshRate= */ baseModeRefreshRate);
     }
 
-    private Vote(int width, int height,
+    private Vote(int minWidth, int minHeight,
+            int width, int height,
             float minPhysicalRefreshRate,
             float maxPhysicalRefreshRate,
             float minRenderFrameRate,
             float maxRenderFrameRate,
             boolean disableRefreshRateSwitching,
             float baseModeRefreshRate) {
+        this.minWidth = minWidth;
+        this.minHeight = minHeight;
         this.width = width;
         this.height = height;
         this.refreshRateRanges = new SurfaceControl.RefreshRateRanges(
@@ -215,6 +275,12 @@ final class Vote {
                 return "PRIORITY_UDFPS";
             case PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE:
                 return "PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE";
+            case PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE:
+                return "PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE";
+            case PRIORITY_LIMIT_MODE:
+                return "PRIORITY_LIMIT_MODE";
+            case PRIORITY_SYNCHRONIZED_REFRESH_RATE:
+                return "PRIORITY_SYNCHRONIZED_REFRESH_RATE";
             case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE:
                 return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE";
             case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE:
@@ -229,9 +295,29 @@ final class Vote {
     @Override
     public String toString() {
         return "Vote: {"
-                + "width: " + width + ", height: " + height
+                + "minWidth: " + minWidth + ", minHeight: " + minHeight
+                + ", width: " + width + ", height: " + height
                 + ", refreshRateRanges: " + refreshRateRanges
                 + ", disableRefreshRateSwitching: " + disableRefreshRateSwitching
                 + ", appRequestBaseModeRefreshRate: "  + appRequestBaseModeRefreshRate + "}";
     }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(minWidth, minHeight, width, height, refreshRateRanges,
+                disableRefreshRateSwitching, appRequestBaseModeRefreshRate);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof Vote)) return false;
+        final var vote = (Vote) o;
+        return  minWidth == vote.minWidth && minHeight == vote.minHeight
+                && width == vote.width && height == vote.height
+                && disableRefreshRateSwitching == vote.disableRefreshRateSwitching
+                && Float.compare(vote.appRequestBaseModeRefreshRate,
+                        appRequestBaseModeRefreshRate) == 0
+                && refreshRateRanges.equals(vote.refreshRateRanges);
+    }
 }
diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java
index bdd2ab7d63b44aa061c25816971364b3a80f7468..49c587aa559630181f9300b64ae03a4254fe928c 100644
--- a/services/core/java/com/android/server/display/mode/VotesStorage.java
+++ b/services/core/java/com/android/server/display/mode/VotesStorage.java
@@ -31,7 +31,8 @@ class VotesStorage {
     private static final String TAG = "VotesStorage";
     // Special ID used to indicate that given vote is to be applied globally, rather than to a
     // specific display.
-    private static final int GLOBAL_ID = -1;
+    @VisibleForTesting
+    static final int GLOBAL_ID = -1;
 
     private boolean mLoggingEnabled;
 
@@ -91,6 +92,7 @@ class VotesStorage {
                     + ", vote=" + vote);
             return;
         }
+        boolean changed = false;
         SparseArray<Vote> votes;
         synchronized (mStorageLock) {
             if (mVotesByDisplay.contains(displayId)) {
@@ -99,10 +101,13 @@ class VotesStorage {
                 votes = new SparseArray<>();
                 mVotesByDisplay.put(displayId, votes);
             }
-            if (vote != null) {
+            var currentVote = votes.get(priority);
+            if (vote != null && !vote.equals(currentVote)) {
                 votes.put(priority, vote);
-            } else {
+                changed = true;
+            } else if (vote == null && currentVote != null) {
                 votes.remove(priority);
+                changed = true;
             }
         }
         Trace.traceCounter(Trace.TRACE_TAG_POWER,
@@ -111,7 +116,9 @@ class VotesStorage {
         if (mLoggingEnabled) {
             Slog.i(TAG, "Updated votes for display=" + displayId + " votes=" + votes);
         }
-        mListener.onChanged();
+        if (changed) {
+            mListener.onChanged();
+        }
     }
 
     /** dump class values, for debugging */
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index c6f6fe2c01f3e4f234949591a6d724db38b15059..fe91050917f56d90657276c8406545b1c937bcdc 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1021,7 +1021,7 @@ abstract public class ManagedServices {
         synchronized (mSnoozing) {
             mSnoozing.remove(user);
         }
-        rebindServices(true, user);
+        unbindUserServices(user);
     }
 
     public void onUserSwitched(int user) {
@@ -1408,12 +1408,24 @@ abstract public class ManagedServices {
     void unbindOtherUserServices(int currentUser) {
         TimingsTraceAndSlog t = new TimingsTraceAndSlog();
         t.traceBegin("ManagedServices.unbindOtherUserServices_current" + currentUser);
-        final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>();
+        unbindServicesImpl(currentUser, true /* allExceptUser */);
+        t.traceEnd();
+    }
+
+    void unbindUserServices(int user) {
+        TimingsTraceAndSlog t = new TimingsTraceAndSlog();
+        t.traceBegin("ManagedServices.unbindUserServices" + user);
+        unbindServicesImpl(user, false /* allExceptUser */);
+        t.traceEnd();
+    }
 
+    void unbindServicesImpl(int user, boolean allExceptUser) {
+        final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>();
         synchronized (mMutex) {
             final Set<ManagedServiceInfo> removableBoundServices = getRemovableConnectedServices();
             for (ManagedServiceInfo info : removableBoundServices) {
-                if (info.userid != currentUser) {
+                if ((allExceptUser && (info.userid != user))
+                        || (!allExceptUser && (info.userid == user))) {
                     Set<ComponentName> toUnbind =
                             componentsToUnbind.get(info.userid, new ArraySet<>());
                     toUnbind.add(info.component);
@@ -1422,7 +1434,6 @@ abstract public class ManagedServices {
             }
         }
         unbindFromServices(componentsToUnbind);
-        t.traceEnd();
     }
 
     protected void unbindFromServices(SparseArray<Set<ComponentName>> componentsToUnbind) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index dc75a98c5bdcc2b1ab3e254c114759b0939c649f..097656cac7f7b135cbb2f0312754764dcd5e8257 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1983,7 +1983,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
             public void run() {
                 if (mPendingHomeKeyEvent != null) {
                     handleShortPressOnHome(mPendingHomeKeyEvent);
-                    mPendingHomeKeyEvent.recycle();
                     mPendingHomeKeyEvent = null;
                 }
             }
@@ -2028,7 +2027,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                     if (mDoubleTapOnHomeBehavior != DOUBLE_TAP_HOME_PIP_MENU
                             || mPictureInPictureVisible) {
                         mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); // just in case
-                        mPendingHomeKeyEvent = KeyEvent.obtain(event);
+                        mPendingHomeKeyEvent = event;
                         mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,
                                 ViewConfiguration.getDoubleTapTimeout());
                         return true;
@@ -2036,11 +2035,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                 }
 
                 // Post to main thread to avoid blocking input pipeline.
-                final KeyEvent shortPressEvent = KeyEvent.obtain(event);
-                mHandler.post(() -> {
-                    handleShortPressOnHome(shortPressEvent);
-                    shortPressEvent.recycle();
-                });
+                mHandler.post(() -> handleShortPressOnHome(event));
                 return true;
             }
 
@@ -2067,14 +2062,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
             if (repeatCount == 0) {
                 mHomePressed = true;
                 if (mPendingHomeKeyEvent != null) {
-                    mPendingHomeKeyEvent.recycle();
                     mPendingHomeKeyEvent = null;
                     mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable);
-                    final KeyEvent doublePressEvent = KeyEvent.obtain(event);
-                    mHandler.post(() -> {
-                        handleDoubleTapOnHome(doublePressEvent);
-                        doublePressEvent.recycle();
-                    });
+                    mHandler.post(() -> handleDoubleTapOnHome(event));
                 // TODO(multi-display): Remove display id check once we support recents on
                 // multi-display
                 } else if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI
@@ -2084,11 +2074,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
             } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
                 if (!keyguardOn) {
                     // Post to main thread to avoid blocking input pipeline.
-                    final KeyEvent longPressEvent = KeyEvent.obtain(event);
-                    mHandler.post(() -> {
-                        handleLongPressOnHome(longPressEvent);
-                        longPressEvent.recycle();
-                    });
+                    mHandler.post(() -> handleLongPressOnHome(event));
                 }
             }
             return true;
diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
index 96f4a01f7f3a48823f5cd0635338c39d20c0ad51..c2666f63d7a6702aea966d2163838e1cc03765e0 100644
--- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
+++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
@@ -297,7 +297,8 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn
                     return;
                 }
 
-                for (ClientState clientState : mClients.values()) {
+
+                for (ClientState clientState : mClients.values().toArray(new ClientState[0])) {
                     tryRespondWithError(
                             clientState.mDelegatingListener.mRemoteListener,
                             SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 4b463c7e17569166aea1ea4645dbece153e0d2bd..b87e761e579551a5c685af522651f1af01b54b15 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -275,15 +275,18 @@ class AsyncRotationController extends FadeAnimationController implements Consume
             // The previous animation leash will be dropped when preparing fade-in animation, so
             // simply apply new animation without restoring the transformation.
             fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM);
-        } else if (op.mAction == Operation.ACTION_SEAMLESS
-                && op.mLeash != null && op.mLeash.isValid()) {
+        } else if (op.isValidSeamless()) {
             if (DEBUG) Slog.d(TAG, "finishOp undo seamless " + windowToken.getTopChild());
             final SurfaceControl.Transaction t = windowToken.getSyncTransaction();
-            t.setMatrix(op.mLeash, 1, 0, 0, 1);
-            t.setPosition(op.mLeash, 0, 0);
+            clearTransform(t, op.mLeash);
         }
     }
 
+    private static void clearTransform(SurfaceControl.Transaction t, SurfaceControl sc) {
+        t.setMatrix(sc, 1, 0, 0, 1);
+        t.setPosition(sc, 0, 0);
+    }
+
     /**
      * Completes all operations such as applying fade-in animation on the previously hidden window
      * tokens. This is called if all windows are ready in new rotation or timed out.
@@ -400,7 +403,19 @@ class AsyncRotationController extends FadeAnimationController implements Consume
                 synchronized (mService.mGlobalLock) {
                     Slog.i(TAG, "Async rotation timeout: " + (!mIsStartTransactionCommitted
                             ? " start transaction is not committed" : mTargetWindowTokens));
-                    mIsStartTransactionCommitted = true;
+                    if (!mIsStartTransactionCommitted) {
+                        // The transaction commit timeout will be handled by:
+                        // 1. BLASTSyncEngine will notify onTransactionCommitTimeout() and then
+                        //    apply the start transaction of transition.
+                        // 2. The TransactionCommittedListener in setupStartTransaction() will be
+                        //    notified to finish the operations of mTargetWindowTokens.
+                        // 3. The slow remote side will also apply the start transaction which may
+                        //    contain stale surface transform.
+                        // 4. Finally, the slow remote reports transition finished. The cleanup
+                        //    transaction from step (1) will be applied when finishing transition,
+                        //    which will recover the stale state from (3).
+                        return;
+                    }
                     mDisplayContent.finishAsyncRotationIfPossible();
                     mService.mWindowPlacerLocked.performSurfacePlacement();
                 }
@@ -539,6 +554,20 @@ class AsyncRotationController extends FadeAnimationController implements Consume
         });
     }
 
+    /** Called when the start transition is ready, but it is not applied in time. */
+    void onTransactionCommitTimeout(SurfaceControl.Transaction t) {
+        if (mIsStartTransactionCommitted) return;
+        for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+            final Operation op = mTargetWindowTokens.valueAt(i);
+            op.mIsCompletionPending = true;
+            if (op.isValidSeamless()) {
+                Slog.d(TAG, "Transaction timeout. Clear transform for "
+                        + mTargetWindowTokens.keyAt(i).getTopChild());
+                clearTransform(t, op.mLeash);
+            }
+        }
+    }
+
     /** Called when the transition by shell is done. */
     void onTransitionFinished() {
         if (mTransitionOp == OP_CHANGE) {
@@ -681,6 +710,10 @@ class AsyncRotationController extends FadeAnimationController implements Consume
             mAction = action;
         }
 
+        boolean isValidSeamless() {
+            return mAction == ACTION_SEAMLESS && mLeash != null && mLeash.isValid();
+        }
+
         @Override
         public String toString() {
             return "Operation{a=" + mAction + " pending=" + mIsCompletionPending + '}';
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 98ee98ba67f74acb29eca2c08f5903cba9128a37..444470952c5dcbdf7ab98fe6db3fba7aa16b8820 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -95,6 +95,7 @@ class BLASTSyncEngine {
 
     interface TransactionReadyListener {
         void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction);
+        default void onTransactionCommitTimeout() {}
     }
 
     /**
@@ -249,6 +250,7 @@ class BLASTSyncEngine {
                            " commit callback. Application ANR likely to follow.");
                     Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                     synchronized (mWm.mGlobalLock) {
+                        mListener.onTransactionCommitTimeout();
                         onCommitted(merged.mNativeObject != 0
                                 ? merged : mWm.mTransactionFactory.get());
                     }
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 4bafccdaaec6331d4b5f70c5fed66bd6e3670db3..e97bda218dcd210a30555ca125eedaf827ed9be8 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -418,7 +418,7 @@ public class BackgroundActivityStartController {
             callerAppUid = realCallingUid;
         }
         // don't abort if the callerApp or other processes of that uid are allowed in any way
-        if (callerApp != null && useCallingUidState) {
+        if (callerApp != null && (useCallingUidState || callerAppBasedOnPiSender)) {
             // first check the original calling process
             final @BalCode int balAllowedForCaller = callerApp
                     .areBackgroundActivityStartsAllowed(appSwitchState);
@@ -509,6 +509,7 @@ public class BackgroundActivityStartController {
                 + "; intent: " + intent
                 + "; callerApp: " + callerApp
                 + "; inVisibleTask: " + (callerApp != null && callerApp.hasActivityInVisibleTask())
+                + "; resultIfPiSenderAllowsBal: " + balCodeToString(resultIfPiSenderAllowsBal)
                 + "]";
         if (resultIfPiSenderAllowsBal != BAL_BLOCK) {
             // We should have returned before if !logVerdictChangeByPiDefaultChange
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index d461d1ec02f0b6482074af4fb800358f22500d87..a1b8949c258221880be13743a7d0a117557de439 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -130,6 +130,7 @@ public class DisplayRotation {
     private final int mUndockedHdmiRotation;
     private final RotationAnimationPair mTmpRotationAnim = new RotationAnimationPair();
     private final RotationHistory mRotationHistory = new RotationHistory();
+    private final RotationLockHistory mRotationLockHistory = new RotationLockHistory();
 
     private OrientationListener mOrientationListener;
     private StatusBarManagerInternal mStatusBarManagerInternal;
@@ -922,7 +923,8 @@ public class DisplayRotation {
     }
 
     @VisibleForTesting
-    void setUserRotation(int userRotationMode, int userRotation) {
+    void setUserRotation(int userRotationMode, int userRotation, String caller) {
+        mRotationLockHistory.addRecord(userRotationMode, userRotation, caller);
         mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED;
         if (useDefaultSettingsProvider()) {
             // We'll be notified via settings listener, so we don't need to update internal values.
@@ -953,17 +955,17 @@ public class DisplayRotation {
         }
     }
 
-    void freezeRotation(int rotation) {
+    void freezeRotation(int rotation, String caller) {
         if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) {
             rotation = RotationUtils.reverseRotationDirectionAroundZAxis(rotation);
         }
 
         rotation = (rotation == -1) ? mRotation : rotation;
-        setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation);
+        setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation, caller);
     }
 
-    void thawRotation() {
-        setUserRotation(WindowManagerPolicy.USER_ROTATION_FREE, mUserRotation);
+    void thawRotation(String caller) {
+        setUserRotation(WindowManagerPolicy.USER_ROTATION_FREE, mUserRotation, caller);
     }
 
     boolean isRotationFrozen() {
@@ -1712,6 +1714,15 @@ public class DisplayRotation {
                 r.dump(prefix, pw);
             }
         }
+
+        if (!mRotationLockHistory.mRecords.isEmpty()) {
+            pw.println();
+            pw.println(prefix + "  RotationLockHistory");
+            prefix = "    " + prefix;
+            for (RotationLockHistory.Record r : mRotationLockHistory.mRecords) {
+                r.dump(prefix, pw);
+            }
+        }
     }
 
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
@@ -2133,6 +2144,40 @@ public class DisplayRotation {
         }
     }
 
+    private static class RotationLockHistory {
+        private static final int MAX_SIZE = 8;
+
+        private static class Record {
+            @WindowManagerPolicy.UserRotationMode final int mUserRotationMode;
+            @Surface.Rotation final int mUserRotation;
+            final String mCaller;
+            final long mTimestamp = System.currentTimeMillis();
+
+            private Record(int userRotationMode, int userRotation, String caller) {
+                mUserRotationMode = userRotationMode;
+                mUserRotation = userRotation;
+                mCaller = caller;
+            }
+
+            void dump(String prefix, PrintWriter pw) {
+                pw.println(prefix + TimeUtils.logTimeOfDay(mTimestamp) + ": "
+                        + "mode="  + WindowManagerPolicy.userRotationModeToString(mUserRotationMode)
+                        + ", rotation=" + Surface.rotationToString(mUserRotation)
+                        + ", caller=" + mCaller);
+            }
+        }
+
+        private final ArrayDeque<RotationLockHistory.Record> mRecords = new ArrayDeque<>(MAX_SIZE);
+
+        void addRecord(@WindowManagerPolicy.UserRotationMode int userRotationMode,
+                @Surface.Rotation int userRotation, String caller) {
+            if (mRecords.size() >= MAX_SIZE) {
+                mRecords.removeFirst();
+            }
+            mRecords.addLast(new Record(userRotationMode, userRotation, caller));
+        }
+    }
+
     private static class RotationHistory {
         private static final int MAX_SIZE = 8;
         private static final int NO_FOLD_CONTROLLER = -2;
diff --git a/services/core/java/com/android/server/wm/DisplayRotationReversionController.java b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java
index d3a8a82f8f871210a6e6f4e6f259e0e17d9e8129..4eb2d88cb4eb4bca621ababe443de5f6a5f0c289 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationReversionController.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java
@@ -118,7 +118,8 @@ final class DisplayRotationReversionController {
         if (mDisplayContent.getDisplayRotation().isRotationFrozen()) {
             mDisplayContent.getDisplayRotation().setUserRotation(
                     mUserRotationModeOverridden,
-                    mUserRotationOverridden);
+                    mUserRotationOverridden,
+                    /* caller= */ "DisplayRotationReversionController#revertOverride");
             return true;
         } else {
             return false;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 0674ec1e8b8260a8bdc07cadc09f4178e981bad6..bbe44c540c39ec366c0e39ed558590ec709e4714 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -42,6 +42,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.ClipData;
@@ -100,6 +101,8 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
     final IWindowSessionCallback mCallback;
     final int mUid;
     final int mPid;
+    @NonNull
+    final WindowProcessController mProcess;
     private final String mStringName;
     SurfaceSession mSurfaceSession;
     private int mNumWindow = 0;
@@ -126,11 +129,23 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
     final boolean mSetsUnrestrictedKeepClearAreas;
 
     public Session(WindowManagerService service, IWindowSessionCallback callback) {
+        this(service, callback, Binder.getCallingPid(), Binder.getCallingUid());
+    }
+
+    @VisibleForTesting
+    Session(WindowManagerService service, IWindowSessionCallback callback,
+            int callingPid, int callingUid) {
         mService = service;
         mCallback = callback;
-        mUid = Binder.getCallingUid();
-        mPid = Binder.getCallingPid();
-        mLastReportedAnimatorScale = service.getCurrentAnimatorScale();
+        mPid = callingPid;
+        mUid = callingUid;
+        synchronized (service.mGlobalLock) {
+            mLastReportedAnimatorScale = service.getCurrentAnimatorScale();
+            mProcess = service.mAtmService.mProcessMap.getProcess(mPid);
+        }
+        if (mProcess == null) {
+            throw new IllegalStateException("Unknown pid=" + mPid + " uid=" + mUid);
+        }
         mCanAddInternalSystemWindow = service.mContext.checkCallingOrSelfPermission(
                 INTERNAL_SYSTEM_WINDOW) == PERMISSION_GRANTED;
         mCanForceShowingInsets = service.mAtmService.isCallerRecents(mUid)
@@ -715,13 +730,8 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
 
     void windowAddedLocked() {
         if (mPackageName == null) {
-            final WindowProcessController wpc = mService.mAtmService.mProcessMap.getProcess(mPid);
-            if (wpc != null) {
-                mPackageName = wpc.mInfo.packageName;
-                mRelayoutTag = "relayoutWindow: " + mPackageName;
-            } else {
-                Slog.e(TAG_WM, "Unknown process pid=" + mPid);
-            }
+            mPackageName = mProcess.mInfo.packageName;
+            mRelayoutTag = "relayoutWindow: " + mPackageName;
         }
         if (mSurfaceSession == null) {
             if (DEBUG) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index bbafa25eac5b409ba3736d3f33c41abadc949016..fac98b84d976f4eff9e0a49ba5c141c62cb6c9b8 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1683,6 +1683,18 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
         }
     }
 
+    @Override
+    public void onTransactionCommitTimeout() {
+        if (mCleanupTransaction == null) return;
+        for (int i = mTargetDisplays.size() - 1; i >= 0; --i) {
+            final DisplayContent dc = mTargetDisplays.get(i);
+            final AsyncRotationController asyncRotationController = dc.getAsyncRotationController();
+            if (asyncRotationController != null && containsChangeFor(dc, mTargets)) {
+                asyncRotationController.onTransactionCommitTimeout(mCleanupTransaction);
+            }
+        }
+    }
+
     /**
      * Collect tasks which moved-to-top as part of this transition. This also updates the
      * controller's latest-reported when relevant.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9a1920d7a217e293c650342d8716b0dc70c3d5e8..a6d285a110f901c05e80ca31912d0035540b371c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4281,8 +4281,8 @@ public class WindowManagerService extends IWindowManager.Stub
     }
 
     @Override
-    public void freezeRotation(int rotation) {
-        freezeDisplayRotation(Display.DEFAULT_DISPLAY, rotation);
+    public void freezeRotation(int rotation, String caller) {
+        freezeDisplayRotation(Display.DEFAULT_DISPLAY, rotation, caller);
     }
 
     /**
@@ -4292,7 +4292,7 @@ public class WindowManagerService extends IWindowManager.Stub
      * @param rotation The desired rotation to freeze to, or -1 to use the current rotation.
      */
     @Override
-    public void freezeDisplayRotation(int displayId, int rotation) {
+    public void freezeDisplayRotation(int displayId, int rotation, String caller) {
         // TODO(multi-display): Track which display is rotated.
         if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
                 "freezeRotation()")) {
@@ -4302,6 +4302,9 @@ public class WindowManagerService extends IWindowManager.Stub
             throw new IllegalArgumentException("Rotation argument must be -1 or a valid "
                     + "rotation constant.");
         }
+        ProtoLog.v(WM_DEBUG_ORIENTATION,
+                "freezeDisplayRotation: current rotation=%d, new rotation=%d, caller=%s",
+                getDefaultDisplayRotation(), rotation, caller);
 
         final long origId = Binder.clearCallingIdentity();
         try {
@@ -4311,7 +4314,7 @@ public class WindowManagerService extends IWindowManager.Stub
                     Slog.w(TAG, "Trying to freeze rotation for a missing display.");
                     return;
                 }
-                display.getDisplayRotation().freezeRotation(rotation);
+                display.getDisplayRotation().freezeRotation(rotation, caller);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -4321,8 +4324,8 @@ public class WindowManagerService extends IWindowManager.Stub
     }
 
     @Override
-    public void thawRotation() {
-        thawDisplayRotation(Display.DEFAULT_DISPLAY);
+    public void thawRotation(String caller) {
+        thawDisplayRotation(Display.DEFAULT_DISPLAY, caller);
     }
 
     /**
@@ -4330,13 +4333,14 @@ public class WindowManagerService extends IWindowManager.Stub
      * Persists across reboots.
      */
     @Override
-    public void thawDisplayRotation(int displayId) {
+    public void thawDisplayRotation(int displayId, String caller) {
         if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
                 "thawRotation()")) {
             throw new SecurityException("Requires SET_ORIENTATION permission");
         }
 
-        ProtoLog.v(WM_DEBUG_ORIENTATION, "thawRotation: mRotation=%d", getDefaultDisplayRotation());
+        ProtoLog.v(WM_DEBUG_ORIENTATION, "thawRotation: mRotation=%d, caller=%s",
+                getDefaultDisplayRotation(), caller);
 
         final long origId = Binder.clearCallingIdentity();
         try {
@@ -4346,7 +4350,7 @@ public class WindowManagerService extends IWindowManager.Stub
                     Slog.w(TAG, "Trying to thaw rotation for a missing display.");
                     return;
                 }
-                display.getDisplayRotation().thawRotation();
+                display.getDisplayRotation().thawRotation(caller);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 74a0bafd3a4c10d0fda4fe56348534a5c291df94..8fad9509af44520b76272f13e77a217ce801a3f3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -438,7 +438,8 @@ public class WindowManagerShellCommand extends ShellCommand {
         }
 
         if ("free".equals(lockMode)) {
-            mInternal.thawDisplayRotation(displayId);
+            mInternal.thawDisplayRotation(displayId,
+                    /* caller= */ "WindowManagerShellCommand#free");
             return 0;
         }
 
@@ -451,7 +452,8 @@ public class WindowManagerShellCommand extends ShellCommand {
         try {
             final int rotation =
                     arg != null ? Integer.parseInt(arg) : -1 /* lock to current rotation */;
-            mInternal.freezeDisplayRotation(displayId, rotation);
+            mInternal.freezeDisplayRotation(displayId, rotation,
+                    /* caller= */ "WindowManagerShellCommand#lock");
             return 0;
         } catch (IllegalArgumentException e) {
             getErrPrintWriter().println("Error: " + e.getMessage());
@@ -1433,7 +1435,8 @@ public class WindowManagerShellCommand extends ShellCommand {
         mInterface.setForcedDisplayScalingMode(displayId, DisplayContent.FORCE_SCALING_MODE_AUTO);
 
         // user-rotation
-        mInternal.thawDisplayRotation(displayId);
+        mInternal.thawDisplayRotation(displayId,
+                /* caller= */ "WindowManagerShellCommand#runReset");
 
         // fixed-to-user-rotation
         mInterface.setFixedToUserRotation(displayId, IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b12cc0b30f531173f8919d31952d24fd30da0faa..ebef606a8d603bb05af60fb7f2d37c400dacdefc 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -754,8 +754,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
 
     static final int BLAST_TIMEOUT_DURATION = 5000; /* milliseconds */
 
-    private final WindowProcessController mWpcForDisplayAreaConfigChanges;
-
     class DrawHandler {
         Consumer<SurfaceControl.Transaction> mConsumer;
         int mSeqId;
@@ -1129,7 +1127,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
             mBaseLayer = 0;
             mSubLayer = 0;
             mWinAnimator = null;
-            mWpcForDisplayAreaConfigChanges = null;
             mOverrideScale = 1f;
             return;
         }
@@ -1186,11 +1183,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
             ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", this, parentWindow);
             parentWindow.addChild(this, sWindowSubLayerComparator);
         }
-
-        // System process or invalid process cannot register to display area config change.
-        mWpcForDisplayAreaConfigChanges = (s.mPid == MY_PID || s.mPid < 0)
-                ? null
-                : service.mAtmService.getProcessController(s.mPid, s.mUid);
     }
 
     boolean shouldWindowHandleBeTrusted(Session s) {
@@ -3621,14 +3613,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
     /** @return {@code true} if the process registered to a display area as a config listener. */
     private boolean registeredForDisplayAreaConfigChanges() {
         final WindowState parentWindow = getParentWindow();
-        final WindowProcessController wpc = parentWindow != null
-                ? parentWindow.mWpcForDisplayAreaConfigChanges
-                : mWpcForDisplayAreaConfigChanges;
-        return wpc != null && wpc.registeredForDisplayAreaConfigChanges();
+        final Session session = parentWindow != null ? parentWindow.mSession : mSession;
+        if (session.mPid == MY_PID) {
+            // System process cannot register to display area config change.
+            return false;
+        }
+        return session.mProcess.registeredForDisplayAreaConfigChanges();
     }
 
+    @NonNull
     WindowProcessController getProcess() {
-        return mWpcForDisplayAreaConfigChanges;
+        return mSession.mProcess;
     }
 
     /**
diff --git a/services/foldables/devicestateprovider/Android.bp b/services/foldables/devicestateprovider/Android.bp
new file mode 100644
index 0000000000000000000000000000000000000000..34737eff8e6ddb64e86595e85783ec6ea1aae36c
--- /dev/null
+++ b/services/foldables/devicestateprovider/Android.bp
@@ -0,0 +1,13 @@
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library {
+    name: "foldable-device-state-provider",
+    srcs: [
+        "src/**/*.java"
+    ],
+    libs: [
+        "services",
+    ],
+}
diff --git a/services/foldables/devicestateprovider/OWNERS b/services/foldables/devicestateprovider/OWNERS
index b2dcd0c0f3c4818e27a5ed47277632bb7881a836..573284419a484279c6d3fc256b953388d936b522 100644
--- a/services/foldables/devicestateprovider/OWNERS
+++ b/services/foldables/devicestateprovider/OWNERS
@@ -1,6 +1,6 @@
 akulian@google.com
-kennethford@google.com
 jiamingliu@google.com
 kchyn@google.com
+kennethford@google.com
 nickchameyev@google.com
 nicomazz@google.com
\ No newline at end of file
diff --git a/services/foldables/devicestateprovider/README.md b/services/foldables/devicestateprovider/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..90174c0cdfc785a220c4dab31388d64d076d8e00
--- /dev/null
+++ b/services/foldables/devicestateprovider/README.md
@@ -0,0 +1,3 @@
+# Foldable Device State Provider library
+
+This library provides foldable-specific classes that could be used to implement a custom DeviceStateProvider.
\ No newline at end of file
diff --git a/services/foldables/devicestateprovider/TEST_MAPPING b/services/foldables/devicestateprovider/TEST_MAPPING
new file mode 100644
index 0000000000000000000000000000000000000000..47de131803c52c909a01665e50bc385a53cbd3b1
--- /dev/null
+++ b/services/foldables/devicestateprovider/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "foldable-device-state-provider-tests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..aea46d1ce329ce752c7b582d6a01fe6b419c0e42
--- /dev/null
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static android.hardware.SensorManager.SENSOR_DELAY_FASTEST;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.PowerManager;
+import android.hardware.display.DisplayManager;
+import android.os.Trace;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.server.devicestate.DeviceState;
+import com.android.server.devicestate.DeviceStateProvider;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.BooleanSupplier;
+import java.util.function.Function;
+
+/**
+ * Device state provider for foldable devices.
+ *
+ * It is an implementation of {@link DeviceStateProvider} tailored specifically for
+ * foldable devices and allows simple callback-based configuration with hall sensor
+ * and hinge angle sensor values.
+ */
+public final class FoldableDeviceStateProvider implements DeviceStateProvider,
+        SensorEventListener, PowerManager.OnThermalStatusChangedListener,
+       DisplayManager.DisplayListener  {
+
+    private static final String TAG = "FoldableDeviceStateProvider";
+    private static final boolean DEBUG = false;
+
+    // Lock for internal state.
+    private final Object mLock = new Object();
+
+    // List of supported states in ascending order based on their identifier.
+    private final DeviceState[] mOrderedStates;
+
+    // Map of state identifier to a boolean supplier that returns true when all required conditions
+    // are met for the device to be in the state.
+    private final SparseArray<BooleanSupplier> mStateConditions = new SparseArray<>();
+
+    private final Sensor mHingeAngleSensor;
+    private final DisplayManager mDisplayManager;
+    private final Sensor mHallSensor;
+
+    @Nullable
+    @GuardedBy("mLock")
+    private Listener mListener = null;
+    @GuardedBy("mLock")
+    private int mLastReportedState = INVALID_DEVICE_STATE;
+    @GuardedBy("mLock")
+    private SensorEvent mLastHingeAngleSensorEvent = null;
+    @GuardedBy("mLock")
+    private SensorEvent mLastHallSensorEvent = null;
+    @GuardedBy("mLock")
+    private @PowerManager.ThermalStatus
+    int mThermalStatus = PowerManager.THERMAL_STATUS_NONE;
+    @GuardedBy("mLock")
+    private boolean mIsScreenOn = false;
+
+    @GuardedBy("mLock")
+    private boolean mPowerSaveModeEnabled;
+
+    public FoldableDeviceStateProvider(@NonNull Context context,
+            @NonNull SensorManager sensorManager,
+            @NonNull Sensor hingeAngleSensor,
+            @NonNull Sensor hallSensor,
+            @NonNull DisplayManager displayManager,
+            @NonNull DeviceStateConfiguration[] deviceStateConfigurations) {
+
+        Preconditions.checkArgument(deviceStateConfigurations.length > 0,
+                "Device state configurations array must not be empty");
+
+        mHingeAngleSensor = hingeAngleSensor;
+        mHallSensor = hallSensor;
+        mDisplayManager = displayManager;
+
+        sensorManager.registerListener(this, mHingeAngleSensor, SENSOR_DELAY_FASTEST);
+        sensorManager.registerListener(this, mHallSensor, SENSOR_DELAY_FASTEST);
+
+        mOrderedStates = new DeviceState[deviceStateConfigurations.length];
+        for (int i = 0; i < deviceStateConfigurations.length; i++) {
+            final DeviceStateConfiguration configuration = deviceStateConfigurations[i];
+            mOrderedStates[i] = configuration.mDeviceState;
+
+            if (mStateConditions.get(configuration.mDeviceState.getIdentifier()) != null) {
+                throw new IllegalArgumentException("Device state configurations must have unique"
+                        + " device state identifiers, found duplicated identifier: " +
+                        configuration.mDeviceState.getIdentifier());
+            }
+
+            mStateConditions.put(configuration.mDeviceState.getIdentifier(), () ->
+                    configuration.mPredicate.apply(this));
+        }
+
+        mDisplayManager.registerDisplayListener(
+                /* listener = */ this,
+                /* handler= */ null,
+                /* eventsMask= */ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED);
+
+        Arrays.sort(mOrderedStates, Comparator.comparingInt(DeviceState::getIdentifier));
+
+        PowerManager powerManager = context.getSystemService(PowerManager.class);
+        if (powerManager != null) {
+            // If any of the device states are thermal sensitive, i.e. it should be disabled when
+            // the device is overheating, then we will update the list of supported states when
+            // thermal status changes.
+            if (hasThermalSensitiveState(deviceStateConfigurations)) {
+                powerManager.addThermalStatusListener(this);
+            }
+
+            // If any of the device states are power sensitive, i.e. it should be disabled when
+            // power save mode is enabled, then we will update the list of supported states when
+            // power save mode is toggled.
+            if (hasPowerSaveSensitiveState(deviceStateConfigurations)) {
+                IntentFilter filter = new IntentFilter(
+                        PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
+                BroadcastReceiver receiver = new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL.equals(
+                                intent.getAction())) {
+                            onPowerSaveModeChanged(powerManager.isPowerSaveMode());
+                        }
+                    }
+                };
+                context.registerReceiver(receiver, filter);
+            }
+        }
+    }
+
+    @Override
+    public void setListener(Listener listener) {
+        synchronized (mLock) {
+            if (mListener != null) {
+                throw new RuntimeException("Provider already has a listener set.");
+            }
+            mListener = listener;
+        }
+        notifySupportedStatesChanged(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
+        notifyDeviceStateChangedIfNeeded();
+    }
+
+    /** Notifies the listener that the set of supported device states has changed. */
+    private void notifySupportedStatesChanged(@SupportedStatesUpdatedReason int reason) {
+        List<DeviceState> supportedStates = new ArrayList<>();
+        Listener listener;
+        synchronized (mLock) {
+            if (mListener == null) {
+                return;
+            }
+            listener = mListener;
+            for (DeviceState deviceState : mOrderedStates) {
+                if (isThermalStatusCriticalOrAbove(mThermalStatus)
+                        && deviceState.hasFlag(
+                        DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
+                    continue;
+                }
+                if (mPowerSaveModeEnabled && deviceState.hasFlag(
+                        DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
+                    continue;
+                }
+                supportedStates.add(deviceState);
+            }
+        }
+
+        listener.onSupportedDeviceStatesChanged(
+                supportedStates.toArray(new DeviceState[supportedStates.size()]), reason);
+    }
+
+    /** Computes the current device state and notifies the listener of a change, if needed. */
+    void notifyDeviceStateChangedIfNeeded() {
+        int stateToReport = INVALID_DEVICE_STATE;
+        Listener listener;
+        synchronized (mLock) {
+            if (mListener == null) {
+                return;
+            }
+
+            listener = mListener;
+
+            int newState = INVALID_DEVICE_STATE;
+            for (int i = 0; i < mOrderedStates.length; i++) {
+                int state = mOrderedStates[i].getIdentifier();
+                if (DEBUG) {
+                    Slog.d(TAG, "Checking conditions for " + mOrderedStates[i].getName() + "("
+                            + i + ")");
+                }
+                boolean conditionSatisfied;
+                try {
+                    conditionSatisfied = mStateConditions.get(state).getAsBoolean();
+                } catch (IllegalStateException e) {
+                    // Failed to compute the current state based on current available data. Continue
+                    // with the expectation that notifyDeviceStateChangedIfNeeded() will be called
+                    // when a callback with the missing data is triggered. May trigger another state
+                    // change if another state is satisfied currently.
+                    Slog.w(TAG, "Unable to check current state = " + state, e);
+                    dumpSensorValues();
+                    continue;
+                }
+
+                if (conditionSatisfied) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Device State conditions satisfied, transition to " + state);
+                    }
+                    newState = state;
+                    break;
+                }
+            }
+            if (newState == INVALID_DEVICE_STATE) {
+                Slog.e(TAG, "No declared device states match any of the required conditions.");
+                dumpSensorValues();
+            }
+
+            if (newState != INVALID_DEVICE_STATE && newState != mLastReportedState) {
+                mLastReportedState = newState;
+                stateToReport = newState;
+            }
+        }
+
+        if (stateToReport != INVALID_DEVICE_STATE) {
+            listener.onStateChanged(stateToReport);
+        }
+    }
+
+    @Override
+    public void onSensorChanged(SensorEvent event) {
+        synchronized (mLock) {
+            if (event.sensor == mHallSensor) {
+                mLastHallSensorEvent = event;
+            } else if (event.sensor == mHingeAngleSensor) {
+                mLastHingeAngleSensorEvent = event;
+            }
+        }
+        notifyDeviceStateChangedIfNeeded();
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        // Do nothing.
+    }
+
+    private float getSensorValue(@Nullable SensorEvent sensorEvent) {
+        if (sensorEvent == null) {
+            throw new IllegalStateException("Have not received sensor event.");
+        }
+
+        if (sensorEvent.values.length < 1) {
+            throw new IllegalStateException("Values in the sensor event are empty");
+        }
+
+        return sensorEvent.values[0];
+    }
+
+    @GuardedBy("mLock")
+    private void dumpSensorValues() {
+        Slog.i(TAG, "Sensor values:");
+        dumpSensorValues("Hall Sensor", mHallSensor, mLastHallSensorEvent);
+        dumpSensorValues("Hinge Angle Sensor",mHingeAngleSensor, mLastHingeAngleSensorEvent);
+        Slog.i(TAG, "isScreenOn: " + isScreenOn());
+    }
+
+    @GuardedBy("mLock")
+    private void dumpSensorValues(String sensorType, Sensor sensor, @Nullable SensorEvent event) {
+        String sensorString = sensor == null ? "null" : sensor.getName();
+        String eventValues = event == null ? "null" : Arrays.toString(event.values);
+        Slog.i(TAG, sensorType + " : " + sensorString + " : " + eventValues);
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+
+    }
+
+    @Override
+    public void onDisplayChanged(int displayId) {
+        if (displayId == DEFAULT_DISPLAY) {
+            // Could potentially be moved to the background if needed.
+            try {
+                Trace.beginSection("FoldableDeviceStateProvider#onDisplayChanged()");
+                int displayState = mDisplayManager.getDisplay(displayId).getState();
+                synchronized (mLock) {
+                    mIsScreenOn = displayState == Display.STATE_ON;
+                }
+            } finally {
+                Trace.endSection();
+            }
+        }
+    }
+
+    /**
+     * Configuration for a single device state, contains information about the state like
+     * identifier, name, flags and a predicate that should return true if the state should
+     * be selected.
+     */
+    public static class DeviceStateConfiguration {
+        private final DeviceState mDeviceState;
+        private final Function<FoldableDeviceStateProvider, Boolean> mPredicate;
+
+        private DeviceStateConfiguration(DeviceState deviceState,
+                Function<FoldableDeviceStateProvider, Boolean> predicate) {
+            mDeviceState = deviceState;
+            mPredicate = predicate;
+        }
+
+        public static DeviceStateConfiguration createConfig(
+                @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier,
+                @NonNull String name,
+                @DeviceState.DeviceStateFlags int flags,
+                Function<FoldableDeviceStateProvider, Boolean> predicate
+        ) {
+            return new DeviceStateConfiguration(new DeviceState(identifier, name, flags),
+                    predicate);
+        }
+
+        public static DeviceStateConfiguration createConfig(
+                @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier,
+                @NonNull String name,
+                Function<FoldableDeviceStateProvider, Boolean> predicate
+        ) {
+            return new DeviceStateConfiguration(new DeviceState(identifier, name, /* flags= */ 0),
+                    predicate);
+        }
+
+        /**
+         * Creates a device state configuration for a closed tent-mode aware state.
+         *
+         * During tent mode:
+         * - The inner display is OFF
+         * - The outer display is ON
+         * - The device is partially unfolded (left and right edges could be on the table)
+         * In this mode the device the device so it could be used in a posture where both left
+         * and right edges of the unfolded device are on the table.
+         *
+         * The predicate returns false after the hinge angle reaches
+         * {@code tentModeSwitchAngleDegrees}. Then it switches back only when the hinge angle
+         * becomes less than {@code maxClosedAngleDegrees}. Hinge angle is 0 degrees when the device
+         * is fully closed and 180 degrees when it is fully unfolded.
+         *
+         * For example, when tentModeSwitchAngleDegrees = 90 and maxClosedAngleDegrees = 5 degrees:
+         *  - when unfolding the device from fully closed posture (last state == closed or it is
+         *    undefined yet) this state will become not matching after reaching the angle
+         *    of 90 degrees, it allows the device to switch the outer display to the inner display
+         *    only when reaching this threshold
+         *  - when folding (last state != 'closed') this state will become matching after reaching
+         *    the angle less than 5 degrees and when hall sensor detected that the device is closed,
+         *    so the switch from the inner display to the outer will become only when the device
+         *    is fully closed.
+         *
+         * @param identifier state identifier
+         * @param name state name
+         * @param flags state flags
+         * @param minClosedAngleDegrees minimum (inclusive) hinge angle value for the closed state
+         * @param maxClosedAngleDegrees maximum (non-inclusive) hinge angle value for the closed
+         *                              state
+         * @param tentModeSwitchAngleDegrees the angle when this state should switch when unfolding
+         * @return device state configuration
+         */
+        public static DeviceStateConfiguration createTentModeClosedState(
+                @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier,
+                @NonNull String name,
+                @DeviceState.DeviceStateFlags int flags,
+                int minClosedAngleDegrees,
+                int maxClosedAngleDegrees,
+                int tentModeSwitchAngleDegrees
+        ) {
+            return new DeviceStateConfiguration(new DeviceState(identifier, name, flags),
+                    (stateContext) -> {
+                        final boolean hallSensorClosed = stateContext.isHallSensorClosed();
+                        final float hingeAngle = stateContext.getHingeAngle();
+                        final int lastState = stateContext.getLastReportedDeviceState();
+                        final boolean isScreenOn = stateContext.isScreenOn();
+
+                        final int switchingDegrees =
+                                isScreenOn ? tentModeSwitchAngleDegrees : maxClosedAngleDegrees;
+
+                        final int closedDeviceState = identifier;
+                        final boolean isLastStateClosed = lastState == closedDeviceState
+                                || lastState == INVALID_DEVICE_STATE;
+
+                        final boolean shouldBeClosedBecauseTentMode = isLastStateClosed
+                                && hingeAngle >= minClosedAngleDegrees
+                                && hingeAngle < switchingDegrees;
+
+                        final boolean shouldBeClosedBecauseFullyShut = hallSensorClosed
+                                && hingeAngle >= minClosedAngleDegrees
+                                && hingeAngle < maxClosedAngleDegrees;
+
+                        return shouldBeClosedBecauseFullyShut || shouldBeClosedBecauseTentMode;
+                    });
+        }
+    }
+
+    /**
+     * @return Whether the screen is on.
+     */
+    public boolean isScreenOn() {
+        synchronized (mLock) {
+            return mIsScreenOn;
+        }
+    }
+    /**
+     * @return current hinge angle value of a foldable device
+     */
+    public float getHingeAngle() {
+        synchronized (mLock) {
+            return getSensorValue(mLastHingeAngleSensorEvent);
+        }
+    }
+
+    /**
+     * @return true if hall sensor detected that the device is closed (fully shut)
+     */
+    public boolean isHallSensorClosed() {
+        synchronized (mLock) {
+            return getSensorValue(mLastHallSensorEvent) > 0f;
+        }
+    }
+
+    /**
+     * @return last reported device state
+     */
+    public int getLastReportedDeviceState() {
+        synchronized (mLock) {
+            return mLastReportedState;
+        }
+    }
+
+    @VisibleForTesting
+    void onPowerSaveModeChanged(boolean isPowerSaveModeEnabled) {
+        synchronized (mLock) {
+            if (mPowerSaveModeEnabled != isPowerSaveModeEnabled) {
+                mPowerSaveModeEnabled = isPowerSaveModeEnabled;
+                notifySupportedStatesChanged(
+                        isPowerSaveModeEnabled ? SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED
+                                : SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED);
+            }
+        }
+    }
+
+    @Override
+    public void onThermalStatusChanged(@PowerManager.ThermalStatus int thermalStatus) {
+        int previousThermalStatus;
+        synchronized (mLock) {
+            previousThermalStatus = mThermalStatus;
+            mThermalStatus = thermalStatus;
+        }
+
+        boolean isThermalStatusCriticalOrAbove = isThermalStatusCriticalOrAbove(thermalStatus);
+        boolean isPreviousThermalStatusCriticalOrAbove =
+                isThermalStatusCriticalOrAbove(previousThermalStatus);
+        if (isThermalStatusCriticalOrAbove != isPreviousThermalStatusCriticalOrAbove) {
+            Slog.i(TAG, "Updating supported device states due to thermal status change."
+                    + " isThermalStatusCriticalOrAbove: " + isThermalStatusCriticalOrAbove);
+            notifySupportedStatesChanged(
+                    isThermalStatusCriticalOrAbove
+                            ? SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL
+                            : SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL);
+        }
+    }
+
+    private static boolean isThermalStatusCriticalOrAbove(
+            @PowerManager.ThermalStatus int thermalStatus) {
+        switch (thermalStatus) {
+            case PowerManager.THERMAL_STATUS_CRITICAL:
+            case PowerManager.THERMAL_STATUS_EMERGENCY:
+            case PowerManager.THERMAL_STATUS_SHUTDOWN:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static boolean hasThermalSensitiveState(DeviceStateConfiguration[] deviceStates) {
+        for (int i = 0; i < deviceStates.length; i++) {
+            DeviceStateConfiguration state = deviceStates[i];
+            if (state.mDeviceState
+                    .hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasPowerSaveSensitiveState(DeviceStateConfiguration[] deviceStates) {
+        for (int i = 0; i < deviceStates.length; i++) {
+            if (deviceStates[i].mDeviceState
+                    .hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/services/foldables/devicestateprovider/tests/Android.bp b/services/foldables/devicestateprovider/tests/Android.bp
new file mode 100644
index 0000000000000000000000000000000000000000..a8db05e9917945e42e5c379526bb41ac0db18dd7
--- /dev/null
+++ b/services/foldables/devicestateprovider/tests/Android.bp
@@ -0,0 +1,30 @@
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "foldable-device-state-provider-tests",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libmultiplejvmtiagentsinterferenceagent",
+        "libstaticjvmtiagent",
+    ],
+    static_libs: [
+        "services",
+        "foldable-device-state-provider",
+        "androidx.test.rules",
+        "junit",
+        "truth-prebuilt",
+        "mockito-target-extended-minus-junit4",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.ext.junit",
+        "testables",
+    ],
+    test_suites: ["device-tests"]
+}
diff --git a/services/foldables/devicestateprovider/tests/AndroidManifest.xml b/services/foldables/devicestateprovider/tests/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..736613d9ad908a5a3f8be5bf38da42a40862dee2
--- /dev/null
+++ b/services/foldables/devicestateprovider/tests/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.foldablesdevicestatelib.tests">
+
+    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.foldablesdevicestatelib.tests"
+        android:label="Tests for foldable-device-state-provider library">
+    </instrumentation>
+
+</manifest>
\ No newline at end of file
diff --git a/services/foldables/devicestateprovider/tests/AndroidTest.xml b/services/foldables/devicestateprovider/tests/AndroidTest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f5fdac75f7cd1f8493ed0d3b7c9aed1bf9123abb
--- /dev/null
+++ b/services/foldables/devicestateprovider/tests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<configuration description="foldable-device-state-provider tests">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="foldable-device-state-provider-tests.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.foldablesdevicestatelib.tests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..8fa4ce592777efd78393531e305135d1d9f5496d
--- /dev/null
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL;
+import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.STATE_OFF;
+import static android.view.Display.STATE_ON;
+
+import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.nullable;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.input.InputSensorInfo;
+import android.os.PowerManager;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.view.Display;
+
+import com.android.server.devicestate.DeviceState;
+import com.android.server.devicestate.DeviceStateProvider;
+import com.android.server.devicestate.DeviceStateProvider.Listener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.internal.util.reflection.FieldSetter;
+
+/**
+ * Unit tests for {@link FoldableDeviceStateProvider}.
+ * <p/>
+ * Run with <code>atest FoldableDeviceStateProviderTest</code>.
+ */
+@RunWith(AndroidTestingRunner.class)
+public final class FoldableDeviceStateProviderTest {
+
+    private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor = ArgumentCaptor.forClass(
+            DeviceState[].class);
+    @Captor
+    private ArgumentCaptor<Integer> mIntegerCaptor;
+    @Captor
+    private ArgumentCaptor<DisplayManager.DisplayListener> mDisplayListenerCaptor;
+    @Mock
+    private SensorManager mSensorManager;
+    @Mock
+    private Context mContext;
+    @Mock
+    private InputSensorInfo mInputSensorInfo;
+    private Sensor mHallSensor;
+    private Sensor mHingeAngleSensor;
+    @Mock
+    private DisplayManager mDisplayManager;
+    private FoldableDeviceStateProvider mProvider;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mHallSensor = new Sensor(mInputSensorInfo);
+        mHingeAngleSensor = new Sensor(mInputSensorInfo);
+    }
+
+    @Test
+    public void create_emptyConfiguration_throwsException() {
+        assertThrows(IllegalArgumentException.class, this::createProvider);
+    }
+
+    @Test
+    public void create_duplicatedDeviceStateIdentifiers_throwsException() {
+        assertThrows(IllegalArgumentException.class,
+                () -> createProvider(
+                        createConfig(
+                                /* identifier= */ 0, /* name= */ "ONE", (c) -> true),
+                        createConfig(
+                                /* identifier= */ 0, /* name= */ "TWO", (c) -> true)
+                ));
+    }
+
+    @Test
+    public void create_allMatchingStatesDefaultsToTheFirstIdentifier() {
+        createProvider(
+                createConfig(
+                        /* identifier= */ 1, /* name= */ "ONE", (c) -> true),
+                createConfig(
+                        /* identifier= */ 2, /* name= */ "TWO", (c) -> true),
+                createConfig(
+                        /* identifier= */ 3, /* name= */ "THREE", (c) -> true)
+        );
+
+        Listener listener = mock(Listener.class);
+        mProvider.setListener(listener);
+
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
+        final DeviceState[] expectedStates = new DeviceState[]{
+                new DeviceState(1, "ONE", /* flags= */ 0),
+                new DeviceState(2, "TWO", /* flags= */ 0),
+                new DeviceState(3, "THREE", /* flags= */ 0),
+        };
+        assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
+
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(1, mIntegerCaptor.getValue().intValue());
+    }
+
+    @Test
+    public void create_multipleMatchingStatesDefaultsToTheLowestIdentifier() {
+        createProvider(
+                createConfig(
+                        /* identifier= */ 1, /* name= */ "ONE", (c) -> false),
+                createConfig(
+                        /* identifier= */ 3, /* name= */ "THREE", (c) -> false),
+                createConfig(
+                        /* identifier= */ 4, /* name= */ "FOUR", (c) -> true),
+                createConfig(
+                        /* identifier= */ 2, /* name= */ "TWO", (c) -> true)
+        );
+
+        Listener listener = mock(Listener.class);
+        mProvider.setListener(listener);
+
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(2, mIntegerCaptor.getValue().intValue());
+    }
+
+    @Test
+    public void test_hingeAngleUpdatedFirstTime_switchesToMatchingState() throws Exception {
+        createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE",
+                        (c) -> c.getHingeAngle() < 90f),
+                createConfig(/* identifier= */ 2, /* name= */ "TWO",
+                        (c) -> c.getHingeAngle() >= 90f));
+        Listener listener = mock(Listener.class);
+        mProvider.setListener(listener);
+        verify(listener, never()).onStateChanged(anyInt());
+        clearInvocations(listener);
+
+        sendSensorEvent(mHingeAngleSensor, /* value= */ 100f);
+
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(2, mIntegerCaptor.getValue().intValue());
+    }
+
+    @Test
+    public void test_hallSensorUpdatedFirstTime_switchesToMatchingState() throws Exception {
+        createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE",
+                        (c) -> !c.isHallSensorClosed()),
+                createConfig(/* identifier= */ 2, /* name= */ "TWO",
+                        FoldableDeviceStateProvider::isHallSensorClosed));
+        Listener listener = mock(Listener.class);
+        mProvider.setListener(listener);
+        verify(listener, never()).onStateChanged(anyInt());
+        clearInvocations(listener);
+
+        // Hall sensor value '1f' is for the closed state
+        sendSensorEvent(mHallSensor, /* value= */ 1f);
+
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(2, mIntegerCaptor.getValue().intValue());
+    }
+
+    @Test
+    public void test_hingeAngleUpdatedSecondTime_switchesToMatchingState() throws Exception {
+        createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE",
+                        (c) -> c.getHingeAngle() < 90f),
+                createConfig(/* identifier= */ 2, /* name= */ "TWO",
+                        (c) -> c.getHingeAngle() >= 90f));
+        sendSensorEvent(mHingeAngleSensor, /* value= */ 30f);
+        Listener listener = mock(Listener.class);
+        mProvider.setListener(listener);
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(1, mIntegerCaptor.getValue().intValue());
+        clearInvocations(listener);
+
+        sendSensorEvent(mHingeAngleSensor, /* value= */ 100f);
+
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(2, mIntegerCaptor.getValue().intValue());
+    }
+
+    @Test
+    public void test_hallSensorUpdatedSecondTime_switchesToMatchingState() throws Exception {
+        createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE",
+                        (c) -> !c.isHallSensorClosed()),
+                createConfig(/* identifier= */ 2, /* name= */ "TWO",
+                        FoldableDeviceStateProvider::isHallSensorClosed));
+        sendSensorEvent(mHallSensor, /* value= */ 0f);
+        Listener listener = mock(Listener.class);
+        mProvider.setListener(listener);
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(1, mIntegerCaptor.getValue().intValue());
+        clearInvocations(listener);
+
+        // Hall sensor value '1f' is for the closed state
+        sendSensorEvent(mHallSensor, /* value= */ 1f);
+
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(2, mIntegerCaptor.getValue().intValue());
+    }
+
+    @Test
+    public void test_invalidSensorValues_onStateChangedIsNotTriggered() throws Exception {
+        createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE",
+                        (c) -> c.getHingeAngle() < 90f),
+                createConfig(/* identifier= */ 2, /* name= */ "TWO",
+                        (c) -> c.getHingeAngle() >= 90f));
+        Listener listener = mock(Listener.class);
+        mProvider.setListener(listener);
+        clearInvocations(listener);
+
+        // First, switch to a non-default state.
+        sendSensorEvent(mHingeAngleSensor, /* value= */ 100f);
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(2, mIntegerCaptor.getValue().intValue());
+
+        clearInvocations(listener);
+
+        // Then, send an invalid sensor event, verify that onStateChanged() is not triggered.
+        sendInvalidSensorEvent(mHingeAngleSensor);
+
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
+        verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
+    }
+
+    @Test
+    public void test_nullSensorValues_noExceptionThrown() throws Exception {
+        createProvider(createConfig( /* identifier= */ 1, /* name= */ "ONE",
+                        (c) -> c.getHingeAngle() < 90f));
+        sendInvalidSensorEvent(null);
+    }
+
+    @Test
+    public void test_flagDisableWhenThermalStatusCritical() throws Exception {
+        createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED",
+                        (c) -> c.getHingeAngle() < 5f),
+                createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED",
+                        (c) -> c.getHingeAngle() < 90f),
+                createConfig(/* identifier= */ 3, /* name= */ "OPENED",
+                        (c) -> c.getHingeAngle() < 180f),
+                createConfig(/* identifier= */ 4, /* name= */ "THERMAL_TEST",
+                        DeviceState.FLAG_EMULATED_ONLY
+                                | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+                                | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE,
+                        (c) -> true));
+        Listener listener = mock(Listener.class);
+        mProvider.setListener(listener);
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
+        assertArrayEquals(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */),
+                        new DeviceState(4, "THERMAL_TEST",
+                                DeviceState.FLAG_EMULATED_ONLY
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)},
+                mDeviceStateArrayCaptor.getValue());
+        clearInvocations(listener);
+
+        mProvider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_MODERATE);
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
+        clearInvocations(listener);
+
+        // The THERMAL_TEST state should be disabled.
+        mProvider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_CRITICAL);
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL));
+        assertArrayEquals(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */)},
+                mDeviceStateArrayCaptor.getValue());
+        clearInvocations(listener);
+
+        // The THERMAL_TEST state should be re-enabled.
+        mProvider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_LIGHT);
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL));
+        assertArrayEquals(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */),
+                        new DeviceState(4, "THERMAL_TEST",
+                                DeviceState.FLAG_EMULATED_ONLY
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)},
+                mDeviceStateArrayCaptor.getValue());
+    }
+
+    @Test
+    public void test_flagDisableWhenPowerSaveEnabled() throws Exception {
+        createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED",
+                        (c) -> c.getHingeAngle() < 5f),
+                createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED",
+                        (c) -> c.getHingeAngle() < 90f),
+                createConfig(/* identifier= */ 3, /* name= */ "OPENED",
+                        (c) -> c.getHingeAngle() < 180f),
+                createConfig(/* identifier= */ 4, /* name= */ "THERMAL_TEST",
+                        DeviceState.FLAG_EMULATED_ONLY
+                                | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+                                | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE,
+                        (c) -> true));
+        mProvider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */);
+        Listener listener = mock(Listener.class);
+        mProvider.setListener(listener);
+
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
+        assertArrayEquals(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */),
+                        new DeviceState(4, "THERMAL_TEST",
+                                DeviceState.FLAG_EMULATED_ONLY
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+                mDeviceStateArrayCaptor.getValue());
+        clearInvocations(listener);
+
+        mProvider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */);
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
+        clearInvocations(listener);
+
+        // The THERMAL_TEST state should be disabled due to power save being enabled.
+        mProvider.onPowerSaveModeChanged(true /* isPowerSaveModeEnabled */);
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED));
+        assertArrayEquals(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */) },
+                mDeviceStateArrayCaptor.getValue());
+        clearInvocations(listener);
+
+        // The THERMAL_TEST state should be re-enabled.
+        mProvider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */);
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED));
+        assertArrayEquals(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */),
+                        new DeviceState(4, "THERMAL_TEST",
+                                DeviceState.FLAG_EMULATED_ONLY
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+                mDeviceStateArrayCaptor.getValue());
+    }
+
+    @Test
+    public void test_previousStateBasedPredicate() {
+        // Create a configuration where state TWO could be matched only if
+        // the previous state was 'THREE'
+        createProvider(
+                createConfig(
+                        /* identifier= */ 1, /* name= */ "ONE", (c) -> c.getHingeAngle() < 30f),
+                createConfig(
+                        /* identifier= */ 2, /* name= */ "TWO",
+                        (c) -> c.getLastReportedDeviceState() == 3 && c.getHingeAngle() > 120f),
+                createConfig(
+                        /* identifier= */ 3, /* name= */ "THREE",
+                        (c) -> c.getHingeAngle() > 90f)
+        );
+        sendSensorEvent(mHingeAngleSensor, /* value= */ 0f);
+        Listener listener = mock(Listener.class);
+        mProvider.setListener(listener);
+
+        // Check that the initial state is 'ONE'
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(1, mIntegerCaptor.getValue().intValue());
+        clearInvocations(listener);
+
+        // Should not match state 'TWO', it should match only state 'THREE'
+        // (because the previous state is not 'THREE', it is 'ONE')
+        sendSensorEvent(mHingeAngleSensor, /* value= */ 180f);
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(3, mIntegerCaptor.getValue().intValue());
+        clearInvocations(listener);
+
+        // Now it should match state 'TWO'
+        // (because the previous state is 'THREE' now)
+        sendSensorEvent(mHingeAngleSensor, /* value= */ 180f);
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(2, mIntegerCaptor.getValue().intValue());
+    }
+
+    @Test
+    public void isScreenOn_afterDisplayChangedToOn_returnsTrue() {
+        createProvider(
+                createConfig(
+                        /* identifier= */ 1, /* name= */ "ONE", (c) -> true)
+        );
+
+        setScreenOn(true);
+
+        assertThat(mProvider.isScreenOn()).isTrue();
+    }
+
+    @Test
+    public void isScreenOn_afterDisplayChangedToOff_returnsFalse() {
+        createProvider(
+                createConfig(
+                        /* identifier= */ 1, /* name= */ "ONE", (c) -> true)
+        );
+
+        setScreenOn(false);
+
+        assertThat(mProvider.isScreenOn()).isFalse();
+    }
+
+    @Test
+    public void isScreenOn_afterDisplayChangedToOnThenOff_returnsFalse() {
+        createProvider(
+                createConfig(
+                        /* identifier= */ 1, /* name= */ "ONE", (c) -> true)
+        );
+
+        setScreenOn(true);
+        setScreenOn(false);
+
+        assertThat(mProvider.isScreenOn()).isFalse();
+    }
+
+    private void setScreenOn(boolean isOn) {
+        Display mockDisplay = mock(Display.class);
+        int state = isOn ? STATE_ON : STATE_OFF;
+        when(mockDisplay.getState()).thenReturn(state);
+        when(mDisplayManager.getDisplay(eq(DEFAULT_DISPLAY))).thenReturn(mockDisplay);
+        mDisplayListenerCaptor.getValue().onDisplayChanged(DEFAULT_DISPLAY);
+    }
+
+    private void sendSensorEvent(Sensor sensor, float value) {
+        SensorEvent event = mock(SensorEvent.class);
+        event.sensor = sensor;
+        try {
+            FieldSetter.setField(event, event.getClass().getField("values"),
+                    new float[]{value});
+        } catch (NoSuchFieldException e) {
+            e.printStackTrace();
+        }
+
+        mProvider.onSensorChanged(event);
+    }
+
+    private void sendInvalidSensorEvent(Sensor sensor) {
+        SensorEvent event = mock(SensorEvent.class);
+        event.sensor = sensor;
+        try {
+            // Set empty values array to make the event invalid
+            FieldSetter.setField(event, event.getClass().getField("values"),
+                    new float[]{});
+        } catch (NoSuchFieldException e) {
+            e.printStackTrace();
+        }
+        mProvider.onSensorChanged(event);
+    }
+
+    private void createProvider(DeviceStateConfiguration... configurations) {
+        mProvider = new FoldableDeviceStateProvider(mContext, mSensorManager, mHingeAngleSensor,
+                mHallSensor, mDisplayManager, configurations);
+        verify(mDisplayManager)
+                .registerDisplayListener(
+                        mDisplayListenerCaptor.capture(),
+                        nullable(Handler.class),
+                        anyLong());
+    }
+}
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionDefinitionsTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionDefinitionsTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..832136cec792fa549ffaa862f6ed68ab5da0c05d
--- /dev/null
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionDefinitionsTest.kt
@@ -0,0 +1,764 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.test
+
+import android.content.pm.PermissionInfo
+import android.os.Build
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.permission.Permission
+import com.android.server.permission.access.permission.PermissionFlags
+import com.android.server.pm.pkg.PackageState
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Parameterized test for testing permission definitions (adopt permissions, add permission groups,
+ * add permissions, trim permissions, trim permission states and revoke permissions on
+ * package update) for onStorageVolumeAdded() and onPackageAdded() in AppIdPermissionPolicy.
+ *
+ * Note that the evaluatePermissionState() call with permission changes
+ * (i.e. changedPermissionNames in AppIdPermissionPolicy) and the evaluatePermissionState() call
+ * with an installedPackageState is put in this test instead of
+ * AppIdPermissionPolicyPermissionStatesTest because these concepts don't apply to onUserAdded().
+ */
+@RunWith(Parameterized::class)
+class AppIdPermissionPolicyPermissionDefinitionsTest : BaseAppIdPermissionPolicyTest() {
+    @Parameterized.Parameter(0) lateinit var action: Action
+
+    @Test
+    fun testAdoptPermissions_permissionsOfMissingSystemApp_getsAdopted() {
+        testAdoptPermissions(hasMissingPackage = true, isSystem = true)
+
+        assertWithMessage(
+            "After $action is called for a null adopt permission package," +
+                " the permission package name: ${getPermission(PERMISSION_NAME_0)?.packageName}" +
+                " did not match the expected package name: $PACKAGE_NAME_0"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_0)
+    }
+
+    @Test
+    fun testAdoptPermissions_permissionsOfExistingSystemApp_notAdopted() {
+        testAdoptPermissions(isSystem = true)
+
+        assertWithMessage(
+            "After $action is called for a non-null adopt permission" +
+                " package, the permission package name:" +
+                " ${getPermission(PERMISSION_NAME_0)?.packageName} should not match the" +
+                " package name: $PACKAGE_NAME_0"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.packageName)
+            .isNotEqualTo(PACKAGE_NAME_0)
+    }
+
+    @Test
+    fun testAdoptPermissions_permissionsOfNonSystemApp_notAdopted() {
+        testAdoptPermissions(hasMissingPackage = true)
+
+        assertWithMessage(
+            "After $action is called for a non-system adopt permission" +
+                " package, the permission package name:" +
+                " ${getPermission(PERMISSION_NAME_0)?.packageName} should not match the" +
+                " package name: $PACKAGE_NAME_0"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.packageName)
+            .isNotEqualTo(PACKAGE_NAME_0)
+    }
+
+    private fun testAdoptPermissions(
+        hasMissingPackage: Boolean = false,
+        isSystem: Boolean = false
+    ) {
+        val parsedPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_1)
+        val packageToAdoptPermission = if (hasMissingPackage) {
+            mockPackageState(APP_ID_1, PACKAGE_NAME_1, isSystem = isSystem)
+        } else {
+            mockPackageState(
+                APP_ID_1,
+                mockAndroidPackage(
+                    PACKAGE_NAME_1,
+                    permissions = listOf(parsedPermission)
+                ),
+                isSystem = isSystem
+            )
+        }
+        addPackageState(packageToAdoptPermission)
+        addPermission(parsedPermission)
+
+        mutateState {
+            val installedPackage = mockPackageState(
+                APP_ID_0,
+                mockAndroidPackage(
+                    PACKAGE_NAME_0,
+                    permissions = listOf(defaultPermission),
+                    adoptPermissions = listOf(PACKAGE_NAME_1)
+                )
+            )
+            addPackageState(installedPackage, newState)
+            testAction(installedPackage)
+        }
+    }
+
+    @Test
+    fun testPermissionGroupDefinition_newPermissionGroup_getsDeclared() {
+        mutateState {
+            val packageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
+            addPackageState(packageState, newState)
+            testAction(packageState)
+        }
+
+        assertWithMessage(
+            "After $action is called when there is no existing" +
+                " permission groups, the new permission group $PERMISSION_GROUP_NAME_0 is not added"
+        )
+            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.name)
+            .isEqualTo(PERMISSION_GROUP_NAME_0)
+    }
+
+    @Test
+    fun testPermissionGroupDefinition_systemAppTakingOverPermissionGroupDefinition_getsTakenOver() {
+        testTakingOverPermissionAndPermissionGroupDefinitions(newPermissionOwnerIsSystem = true)
+
+        assertWithMessage(
+            "After $action is called when $PERMISSION_GROUP_NAME_0 already" +
+                " exists in the system, the system app $PACKAGE_NAME_0 didn't takeover the" +
+                " ownership of this permission group"
+        )
+            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_0)
+    }
+
+    @Test
+    fun testPermissionGroupDefinition_instantApps_remainsUnchanged() {
+        testTakingOverPermissionAndPermissionGroupDefinitions(
+            newPermissionOwnerIsInstant = true,
+            permissionGroupAlreadyExists = false
+        )
+
+        assertWithMessage(
+            "After $action is called for an instant app," +
+                " the new permission group $PERMISSION_GROUP_NAME_0 should not be added"
+        )
+            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0))
+            .isNull()
+    }
+
+    @Test
+    fun testPermissionGroupDefinition_nonSystemAppTakingOverGroupDefinition_remainsUnchanged() {
+        testTakingOverPermissionAndPermissionGroupDefinitions()
+
+        assertWithMessage(
+            "After $action is called when $PERMISSION_GROUP_NAME_0 already" +
+                " exists in the system, non-system app $PACKAGE_NAME_0 shouldn't takeover" +
+                " ownership of this permission group"
+        )
+            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_1)
+    }
+
+    @Test
+    fun testPermissionGroupDefinition_takingOverGroupDeclaredBySystemApp_remainsUnchanged() {
+        testTakingOverPermissionAndPermissionGroupDefinitions(oldPermissionOwnerIsSystem = true)
+
+        assertWithMessage(
+            "After $action is called when $PERMISSION_GROUP_NAME_0 already" +
+                " exists in the system and is owned by a system app, app $PACKAGE_NAME_0" +
+                " shouldn't takeover ownership of this permission group"
+        )
+            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_1)
+    }
+
+    @Test
+    fun testPermissionDefinition_newPermission_getsDeclared() {
+        mutateState {
+            val packageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
+            addPackageState(packageState, newState)
+            testAction(packageState)
+        }
+
+        assertWithMessage(
+            "After $action is called when there is no existing" +
+                " permissions, the new permission $PERMISSION_NAME_0 is not added"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.name)
+            .isEqualTo(PERMISSION_NAME_0)
+    }
+
+    @Test
+    fun testPermissionDefinition_configPermission_getsTakenOver() {
+        testTakingOverPermissionAndPermissionGroupDefinitions(
+            oldPermissionOwnerIsSystem = true,
+            newPermissionOwnerIsSystem = true,
+            type = Permission.TYPE_CONFIG,
+            isReconciled = false
+        )
+
+        assertWithMessage(
+            "After $action is called for a config permission with" +
+                " no owner, the ownership is not taken over by a system app $PACKAGE_NAME_0"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_0)
+    }
+
+    @Test
+    fun testPermissionDefinition_systemAppTakingOverPermissionDefinition_getsTakenOver() {
+        testTakingOverPermissionAndPermissionGroupDefinitions(newPermissionOwnerIsSystem = true)
+
+        assertWithMessage(
+            "After $action is called when $PERMISSION_NAME_0 already" +
+                " exists in the system, the system app $PACKAGE_NAME_0 didn't takeover ownership" +
+                " of this permission"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_0)
+    }
+
+    @Test
+    fun testPermissionDefinition_nonSystemAppTakingOverPermissionDefinition_remainsUnchanged() {
+        testTakingOverPermissionAndPermissionGroupDefinitions()
+
+        assertWithMessage(
+            "After $action is called when $PERMISSION_NAME_0 already" +
+                " exists in the system, the non-system app $PACKAGE_NAME_0 shouldn't takeover" +
+                " ownership of this permission"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_1)
+    }
+
+    @Test
+    fun testPermissionDefinition_takingOverPermissionDeclaredBySystemApp_remainsUnchanged() {
+        testTakingOverPermissionAndPermissionGroupDefinitions(oldPermissionOwnerIsSystem = true)
+
+        assertWithMessage(
+            "After $action is called when $PERMISSION_NAME_0 already" +
+                " exists in system and is owned by a system app, the $PACKAGE_NAME_0 shouldn't" +
+                " takeover ownership of this permission"
+        )
+            .that(getPermission(PERMISSION_NAME_0)?.packageName)
+            .isEqualTo(PACKAGE_NAME_1)
+    }
+
+    private fun testTakingOverPermissionAndPermissionGroupDefinitions(
+        oldPermissionOwnerIsSystem: Boolean = false,
+        newPermissionOwnerIsSystem: Boolean = false,
+        newPermissionOwnerIsInstant: Boolean = false,
+        permissionGroupAlreadyExists: Boolean = true,
+        permissionAlreadyExists: Boolean = true,
+        type: Int = Permission.TYPE_MANIFEST,
+        isReconciled: Boolean = true,
+    ) {
+        val oldPermissionOwnerPackageState = mockPackageState(
+            APP_ID_1,
+            PACKAGE_NAME_1,
+            isSystem = oldPermissionOwnerIsSystem
+        )
+        addPackageState(oldPermissionOwnerPackageState)
+        if (permissionGroupAlreadyExists) {
+            addPermissionGroup(mockParsedPermissionGroup(PERMISSION_GROUP_NAME_0, PACKAGE_NAME_1))
+        }
+        if (permissionAlreadyExists) {
+            addPermission(
+                mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_1),
+                type = type,
+                isReconciled = isReconciled
+            )
+        }
+
+        mutateState {
+            val newPermissionOwnerPackageState = mockPackageState(
+                APP_ID_0,
+                mockSimpleAndroidPackage(),
+                isSystem = newPermissionOwnerIsSystem,
+                isInstantApp = newPermissionOwnerIsInstant
+            )
+            addPackageState(newPermissionOwnerPackageState, newState)
+            testAction(newPermissionOwnerPackageState)
+        }
+    }
+
+    @Test
+    fun testPermissionChanged_permissionGroupChanged_getsRevoked() {
+        testPermissionChanged(
+            oldPermissionGroup = PERMISSION_GROUP_NAME_1,
+            newPermissionGroup = PERMISSION_GROUP_NAME_0
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package that has a permission group change" +
+                " for a permission it defines, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testPermissionChanged_protectionLevelChanged_getsRevoked() {
+        testPermissionChanged(newProtectionLevel = PermissionInfo.PROTECTION_INTERNAL)
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package that has a protection level change" +
+                " for a permission it defines, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    private fun testPermissionChanged(
+        oldPermissionGroup: String? = null,
+        newPermissionGroup: String? = null,
+        newProtectionLevel: Int = PermissionInfo.PROTECTION_DANGEROUS
+    ) {
+        val oldPermission = mockParsedPermission(
+            PERMISSION_NAME_0,
+            PACKAGE_NAME_0,
+            group = oldPermissionGroup,
+            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS
+        )
+        val oldPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(oldPermission))
+        )
+        addPackageState(oldPackageState)
+        addPermission(oldPermission)
+        setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED)
+
+        mutateState {
+            val newPermission = mockParsedPermission(
+                PERMISSION_NAME_0,
+                PACKAGE_NAME_0,
+                group = newPermissionGroup,
+                protectionLevel = newProtectionLevel
+            )
+            val newPackageState = mockPackageState(
+                APP_ID_0,
+                mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(newPermission))
+            )
+            addPackageState(newPackageState, newState)
+            testAction(newPackageState)
+        }
+    }
+
+    @Test
+    fun testPermissionDeclaration_permissionTreeNoLongerDeclared_getsDefinitionRemoved() {
+        testPermissionDeclaration {}
+
+        assertWithMessage(
+            "After $action is called for a package that no longer defines a permission" +
+                " tree, the permission tree: $PERMISSION_NAME_0 in system state should be removed"
+        )
+            .that(getPermissionTree(PERMISSION_NAME_0))
+            .isNull()
+    }
+
+    @Test
+    fun testPermissionDeclaration_permissionTreeByDisabledSystemPackage_remainsUnchanged() {
+        testPermissionDeclaration {
+            val disabledSystemPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
+            addDisabledSystemPackageState(disabledSystemPackageState)
+        }
+
+        assertWithMessage(
+            "After $action is called for a package that no longer defines" +
+                " a permission tree while this permission tree is still defined by" +
+                " a disabled system package, the permission tree: $PERMISSION_NAME_0 in" +
+                " system state should not be removed"
+        )
+            .that(getPermissionTree(PERMISSION_TREE_NAME))
+            .isNotNull()
+    }
+
+    @Test
+    fun testPermissionDeclaration_permissionNoLongerDeclared_getsDefinitionRemoved() {
+        testPermissionDeclaration {}
+
+        assertWithMessage(
+            "After $action is called for a package that no longer defines a permission," +
+                " the permission: $PERMISSION_NAME_0 in system state should be removed"
+        )
+            .that(getPermission(PERMISSION_NAME_0))
+            .isNull()
+    }
+
+    @Test
+    fun testPermissionDeclaration_permissionByDisabledSystemPackage_remainsUnchanged() {
+        testPermissionDeclaration {
+            val disabledSystemPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
+            addDisabledSystemPackageState(disabledSystemPackageState)
+        }
+
+        assertWithMessage(
+            "After $action is called for a disabled system package and it's updated apk" +
+                " no longer defines a permission, the permission: $PERMISSION_NAME_0 in" +
+                " system state should not be removed"
+        )
+            .that(getPermission(PERMISSION_NAME_0))
+            .isNotNull()
+    }
+
+    private fun testPermissionDeclaration(additionalSetup: () -> Unit) {
+        val oldPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
+        addPackageState(oldPackageState)
+        addPermission(defaultPermissionTree)
+        addPermission(defaultPermission)
+
+        additionalSetup()
+
+        mutateState {
+            val newPackageState = mockPackageState(APP_ID_0, mockAndroidPackage(PACKAGE_NAME_0))
+            addPackageState(newPackageState, newState)
+            testAction(newPackageState)
+        }
+    }
+
+    @Test
+    fun testTrimPermissionStates_permissionsNoLongerRequested_getsFlagsRevoked() {
+        val parsedPermission = mockParsedPermission(
+            PERMISSION_NAME_0,
+            PACKAGE_NAME_0,
+            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS
+        )
+        val oldPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(
+                PACKAGE_NAME_0,
+                permissions = listOf(parsedPermission),
+                requestedPermissions = setOf(PERMISSION_NAME_0)
+            )
+        )
+        addPackageState(oldPackageState)
+        addPermission(parsedPermission)
+        setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED)
+
+        mutateState {
+            val newPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
+            addPackageState(newPackageState, newState)
+            testAction(newPackageState)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package that no longer requests a permission" +
+                " the actual permission flags $actualFlags should match the" +
+                " expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testRevokePermissionsOnPackageUpdate_storageAndMediaDowngradingPastQ_getsRuntimeRevoked() {
+        testRevokePermissionsOnPackageUpdate(
+            PermissionFlags.RUNTIME_GRANTED,
+            newTargetSdkVersion = Build.VERSION_CODES.P
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package that's downgrading past Q" +
+                " the actual permission flags $actualFlags should match the" +
+                " expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testRevokePermissionsOnPackageUpdate_storageAndMediaNotDowngradingPastQ_remainsUnchanged() {
+        val oldFlags = PermissionFlags.RUNTIME_GRANTED
+        testRevokePermissionsOnPackageUpdate(
+            oldFlags,
+            oldTargetSdkVersion = Build.VERSION_CODES.P,
+            newTargetSdkVersion = Build.VERSION_CODES.P
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that's not downgrading past Q" +
+                " the actual permission flags $actualFlags should match the" +
+                " expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testRevokePermissionsOnPackageUpdate_policyFixedDowngradingPastQ_remainsUnchanged() {
+        val oldFlags = PermissionFlags.RUNTIME_GRANTED and PermissionFlags.POLICY_FIXED
+        testRevokePermissionsOnPackageUpdate(oldFlags, newTargetSdkVersion = Build.VERSION_CODES.P)
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that's downgrading past Q" +
+                " the actual permission flags with PermissionFlags.POLICY_FIXED $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testRevokePermissionsOnPackageUpdate_newlyRequestingLegacyExternalStorage_runtimeRevoked() {
+        testRevokePermissionsOnPackageUpdate(
+            PermissionFlags.RUNTIME_GRANTED,
+            oldTargetSdkVersion = Build.VERSION_CODES.P,
+            newTargetSdkVersion = Build.VERSION_CODES.P,
+            oldIsRequestLegacyExternalStorage = false
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package with" +
+                " newlyRequestingLegacyExternalStorage, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testRevokePermissionsOnPackageUpdate_missingOldPackage_remainsUnchanged() {
+        val oldFlags = PermissionFlags.RUNTIME_GRANTED
+        testRevokePermissionsOnPackageUpdate(
+            oldFlags,
+            newTargetSdkVersion = Build.VERSION_CODES.P,
+            isOldPackageMissing = true
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that's downgrading past Q" +
+                " and doesn't have the oldPackage, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    private fun testRevokePermissionsOnPackageUpdate(
+        oldFlags: Int,
+        oldTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+        newTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+        oldIsRequestLegacyExternalStorage: Boolean = true,
+        newIsRequestLegacyExternalStorage: Boolean = true,
+        isOldPackageMissing: Boolean = false
+    ) {
+        val parsedPermission = mockParsedPermission(
+            PERMISSION_READ_EXTERNAL_STORAGE,
+            PACKAGE_NAME_0,
+            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS
+        )
+        val oldPackageState = if (isOldPackageMissing) {
+            mockPackageState(APP_ID_0, PACKAGE_NAME_0)
+        } else {
+            mockPackageState(
+                APP_ID_0,
+                mockAndroidPackage(
+                    PACKAGE_NAME_0,
+                    targetSdkVersion = oldTargetSdkVersion,
+                    isRequestLegacyExternalStorage = oldIsRequestLegacyExternalStorage,
+                    requestedPermissions = setOf(PERMISSION_READ_EXTERNAL_STORAGE),
+                    permissions = listOf(parsedPermission)
+                )
+            )
+        }
+        addPackageState(oldPackageState)
+        addPermission(parsedPermission)
+        setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE, oldFlags)
+
+        mutateState {
+            val newPackageState = mockPackageState(
+                APP_ID_0,
+                mockAndroidPackage(
+                    PACKAGE_NAME_0,
+                    targetSdkVersion = newTargetSdkVersion,
+                    isRequestLegacyExternalStorage = newIsRequestLegacyExternalStorage,
+                    requestedPermissions = setOf(PERMISSION_READ_EXTERNAL_STORAGE),
+                    permissions = listOf(parsedPermission)
+                )
+            )
+            addPackageState(newPackageState, newState)
+            testAction(newPackageState)
+        }
+    }
+
+    @Test
+    fun testEvaluatePermissionState_normalPermissionRequestedByInstalledPackage_getsGranted() {
+        val oldFlags = PermissionFlags.INSTALL_REVOKED
+        val permissionOwnerPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
+        val installedPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+        )
+        addPackageState(permissionOwnerPackageState)
+        addPackageState(installedPackageState)
+        addPermission(defaultPermission)
+        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
+
+        mutateState {
+            testAction(installedPackageState)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a normal permission" +
+                " with the INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags since it's a new install"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    /**
+     * We set up a permission protection level change from SIGNATURE to NORMAL in order to make
+     * the permission a "changed permission" in order to test evaluatePermissionState() called by
+     * evaluatePermissionStateForAllPackages(). This makes the requestingPackageState not the
+     * installedPackageState so that we can test whether requesting by system package will give us
+     * the expected permission flags.
+     *
+     * Besides, this also helps us test evaluatePermissionStateForAllPackages(). Since both
+     * evaluatePermissionStateForAllPackages() and evaluateAllPermissionStatesForPackage() call
+     * evaluatePermissionState() in their implementations, we use these tests as the only tests
+     * that test evaluatePermissionStateForAllPackages()
+     */
+    @Test
+    fun testEvaluatePermissionState_normalPermissionRequestedBySystemPackage_getsGranted() {
+        testEvaluateNormalPermissionStateWithPermissionChanges(requestingPackageIsSystem = true)
+
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
+        assertWithMessage(
+            "After $action is called for a system package that requests a normal" +
+                " permission with INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_normalCompatibilityPermission_getsGranted() {
+        testEvaluateNormalPermissionStateWithPermissionChanges(
+            permissionName = PERMISSION_POST_NOTIFICATIONS,
+            requestingPackageTargetSdkVersion = Build.VERSION_CODES.S
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_POST_NOTIFICATIONS)
+        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a normal compatibility" +
+                " permission with INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_normalPermissionPreviouslyRevoked_getsInstallRevoked() {
+        testEvaluateNormalPermissionStateWithPermissionChanges()
+
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.INSTALL_REVOKED
+        assertWithMessage(
+            "After $action is called for a package that requests a normal" +
+                " permission with INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    private fun testEvaluateNormalPermissionStateWithPermissionChanges(
+        permissionName: String = PERMISSION_NAME_0,
+        requestingPackageTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+        requestingPackageIsSystem: Boolean = false
+    ) {
+        val oldParsedPermission = mockParsedPermission(
+            permissionName,
+            PACKAGE_NAME_0,
+            protectionLevel = PermissionInfo.PROTECTION_SIGNATURE
+        )
+        val oldPermissionOwnerPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(oldParsedPermission))
+        )
+        val requestingPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(
+                PACKAGE_NAME_1,
+                requestedPermissions = setOf(permissionName),
+                targetSdkVersion = requestingPackageTargetSdkVersion
+            ),
+            isSystem = requestingPackageIsSystem,
+        )
+        addPackageState(oldPermissionOwnerPackageState)
+        addPackageState(requestingPackageState)
+        addPermission(oldParsedPermission)
+        val oldFlags = PermissionFlags.INSTALL_REVOKED
+        setPermissionFlags(APP_ID_1, USER_ID_0, permissionName, oldFlags)
+
+        mutateState {
+            val newParsedPermission = mockParsedPermission(permissionName, PACKAGE_NAME_0)
+            val newPermissionOwnerPackageState = mockPackageState(
+                APP_ID_0,
+                mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(newParsedPermission))
+            )
+            addPackageState(newPermissionOwnerPackageState, newState)
+            testAction(newPermissionOwnerPackageState)
+        }
+    }
+
+    private fun MutateStateScope.testAction(packageState: PackageState) {
+        with(appIdPermissionPolicy) {
+            when (action) {
+                Action.ON_PACKAGE_ADDED -> onPackageAdded(packageState)
+                Action.ON_STORAGE_VOLUME_ADDED -> onStorageVolumeMounted(
+                    null,
+                    listOf(packageState.packageName),
+                    true
+                )
+            }
+        }
+    }
+
+    enum class Action { ON_PACKAGE_ADDED, ON_STORAGE_VOLUME_ADDED }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun data(): Array<Action> = Action.values()
+    }
+}
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..823ce4555ca8c4160dba9a1431f76c65605946db
--- /dev/null
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.test
+
+import android.content.pm.PermissionInfo
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.permission.PermissionFlags
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * A parameterized test for testing resetting runtime permissions for onPackageUninstalled()
+ * and resetRuntimePermissions() in AppIdPermissionPolicy
+ */
+@RunWith(Parameterized::class)
+class AppIdPermissionPolicyPermissionResetTest : BaseAppIdPermissionPolicyTest() {
+    @Parameterized.Parameter(0) lateinit var action: Action
+
+    @Test
+    fun testResetRuntimePermissions_runtimeGranted_getsRevoked() {
+        val oldFlags = PermissionFlags.RUNTIME_GRANTED
+        val expectedNewFlags = 0
+        testResetRuntimePermissions(oldFlags, expectedNewFlags)
+    }
+
+    @Test
+    fun testResetRuntimePermissions_roleGranted_getsGranted() {
+        val oldFlags = PermissionFlags.ROLE
+        val expectedNewFlags = PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED
+        testResetRuntimePermissions(oldFlags, expectedNewFlags)
+    }
+
+    @Test
+    fun testResetRuntimePermissions_nullAndroidPackage_remainsUnchanged() {
+        val oldFlags = PermissionFlags.RUNTIME_GRANTED
+        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED
+        testResetRuntimePermissions(oldFlags, expectedNewFlags, isAndroidPackageMissing = true)
+    }
+
+    private fun testResetRuntimePermissions(
+        oldFlags: Int,
+        expectedNewFlags: Int,
+        isAndroidPackageMissing: Boolean = false
+    ) {
+        val parsedPermission = mockParsedPermission(
+            PERMISSION_NAME_0,
+            PACKAGE_NAME_0,
+            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS,
+        )
+        val permissionOwnerPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
+        )
+        val requestingPackageState = if (isAndroidPackageMissing) {
+            mockPackageState(APP_ID_1, PACKAGE_NAME_1)
+        } else {
+            mockPackageState(
+                APP_ID_1,
+                mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+            )
+        }
+        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
+        addPackageState(permissionOwnerPackageState)
+        addPackageState(requestingPackageState)
+        addPermission(parsedPermission)
+
+        mutateState { testAction() }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        assertWithMessage(
+            "After resetting runtime permissions, permission flags did not match" +
+                " expected values: expectedNewFlags is $expectedNewFlags," +
+                " actualFlags is $actualFlags, while the oldFlags is $oldFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    private fun MutateStateScope.testAction(
+        packageName: String = PACKAGE_NAME_1,
+        appId: Int = APP_ID_1,
+        userId: Int = USER_ID_0
+    ) {
+        with(appIdPermissionPolicy) {
+            when (action) {
+                Action.ON_PACKAGE_UNINSTALLED -> onPackageUninstalled(packageName, appId, userId)
+                Action.RESET_RUNTIME_PERMISSIONS -> resetRuntimePermissions(packageName, userId)
+            }
+        }
+    }
+
+    enum class Action { ON_PACKAGE_UNINSTALLED, RESET_RUNTIME_PERMISSIONS }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun data(): Array<Action> = Action.values()
+    }
+}
\ No newline at end of file
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f085bd7fd7f0f73a9060fc3f613a3ef8c28cb662
--- /dev/null
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt
@@ -0,0 +1,884 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.test
+
+import android.content.pm.PermissionInfo
+import android.os.Build
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.immutable.IndexedListSet
+import com.android.server.permission.access.immutable.MutableIndexedListSet
+import com.android.server.permission.access.immutable.MutableIndexedMap
+import com.android.server.permission.access.permission.PermissionFlags
+import com.android.server.pm.permission.PermissionAllowlist
+import com.android.server.pm.pkg.PackageState
+import com.android.server.testutils.mock
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * A parameterized test for testing evaluating permission states and inheriting implicit permission
+ * states for onUserAdded(), onStorageVolumeAdded() and onPackageAdded() in AppIdPermissionPolicy
+ */
+@RunWith(Parameterized::class)
+class AppIdPermissionPolicyPermissionStatesTest : BaseAppIdPermissionPolicyTest() {
+    @Parameterized.Parameter(0) lateinit var action: Action
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+        if (action == Action.ON_USER_ADDED) {
+            createUserState(USER_ID_NEW)
+        }
+    }
+
+    @Test
+    fun testEvaluatePermissionState_normalPermissionAlreadyGranted_remainsUnchanged() {
+        val oldFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.INSTALL_REVOKED
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_NORMAL) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests a normal permission" +
+                " with an existing INSTALL_GRANTED flag, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_normalPermissionNotInstallRevoked_getsGranted() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_NORMAL,
+            isNewInstall = true
+        ) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a normal permission" +
+                " with no existing flags, the actual permission flags $actualFlags" +
+                " should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_normalAppOpPermission_getsRoleAndUserSetFlagsPreserved() {
+        val oldFlags = PermissionFlags.ROLE or PermissionFlags.USER_SET
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_NORMAL or PermissionInfo.PROTECTION_FLAG_APPOP
+        ) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED or oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests a normal app op" +
+                " permission with existing ROLE and USER_SET flags, the actual permission flags" +
+                " $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_internalWasGrantedWithMissingPackage_getsProtectionGranted() {
+        val oldFlags = PermissionFlags.PROTECTION_GRANTED
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_INTERNAL) {
+            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
+            addPackageState(packageStateWithMissingPackage)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests an internal permission" +
+                " with missing android package and $oldFlags flag, the actual permission flags" +
+                " $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_internalAppOpPermission_getsRoleAndUserSetFlagsPreserved() {
+        val oldFlags = PermissionFlags.PROTECTION_GRANTED or PermissionFlags.ROLE or
+            PermissionFlags.USER_SET
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_INTERNAL or PermissionInfo.PROTECTION_FLAG_APPOP
+        ) {
+            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
+            addPackageState(packageStateWithMissingPackage)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests an internal permission" +
+                " with missing android package and $oldFlags flag and the permission isAppOp," +
+                " the actual permission flags $actualFlags should match the expected" +
+                " flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_internalDevelopmentPermission_getsRuntimeGrantedPreserved() {
+        val oldFlags = PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_INTERNAL or PermissionInfo.PROTECTION_FLAG_DEVELOPMENT
+        ) {
+            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
+            addPackageState(packageStateWithMissingPackage)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests an internal permission" +
+                " with missing android package and $oldFlags flag and permission isDevelopment," +
+                " the actual permission flags $actualFlags should match the expected" +
+                " flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_internalRolePermission_getsRoleAndRuntimeGrantedPreserved() {
+        val oldFlags = PermissionFlags.PROTECTION_GRANTED or PermissionFlags.ROLE or
+            PermissionFlags.RUNTIME_GRANTED
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_INTERNAL or PermissionInfo.PROTECTION_FLAG_ROLE
+        ) {
+            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
+            addPackageState(packageStateWithMissingPackage)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests an internal permission" +
+                " with missing android package and $oldFlags flag and the permission isRole," +
+                " the actual permission flags $actualFlags should match the expected" +
+                " flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_signaturePrivilegedPermissionNotAllowlisted_isNotGranted() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_SIGNATURE or PermissionInfo.PROTECTION_FLAG_PRIVILEGED,
+            isInstalledPackageSystem = true,
+            isInstalledPackagePrivileged = true,
+            isInstalledPackageProduct = true,
+            // To mock the return value of shouldGrantPrivilegedOrOemPermission()
+            isInstalledPackageVendor = true,
+            isNewInstall = true
+        ) {
+            val platformPackage = mockPackageState(
+                PLATFORM_APP_ID,
+                mockAndroidPackage(PLATFORM_PACKAGE_NAME)
+            )
+            setupAllowlist(PACKAGE_NAME_1, false)
+            addPackageState(platformPackage)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests a signature privileged" +
+                " permission that's not allowlisted, the actual permission" +
+                " flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_nonPrivilegedShouldGrantBySignature_getsProtectionGranted() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_SIGNATURE,
+            isInstalledPackageSystem = true,
+            isInstalledPackagePrivileged = true,
+            isInstalledPackageProduct = true,
+            isInstalledPackageSignatureMatching = true,
+            isInstalledPackageVendor = true,
+            isNewInstall = true
+        ) {
+            val platformPackage = mockPackageState(
+                PLATFORM_APP_ID,
+                mockAndroidPackage(PLATFORM_PACKAGE_NAME, isSignatureMatching = true)
+            )
+            setupAllowlist(PACKAGE_NAME_1, false)
+            addPackageState(platformPackage)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.PROTECTION_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a signature" +
+                " non-privileged permission, the actual permission" +
+                " flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_privilegedAllowlistShouldGrantByProtectionFlags_getsGranted() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_SIGNATURE or PermissionInfo.PROTECTION_FLAG_PRIVILEGED,
+            isInstalledPackageSystem = true,
+            isInstalledPackagePrivileged = true,
+            isInstalledPackageProduct = true,
+            isNewInstall = true
+        ) {
+            val platformPackage = mockPackageState(
+                PLATFORM_APP_ID,
+                mockAndroidPackage(PLATFORM_PACKAGE_NAME)
+            )
+            setupAllowlist(PACKAGE_NAME_1, true)
+            addPackageState(platformPackage)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.PROTECTION_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a signature privileged" +
+                " permission that's allowlisted and should grant by protection flags, the actual" +
+                " permission flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    private fun setupAllowlist(
+        packageName: String,
+        allowlistState: Boolean,
+        state: MutableAccessState = oldState
+    ) {
+        state.mutateExternalState().setPrivilegedPermissionAllowlistPackages(
+            MutableIndexedListSet<String>().apply { add(packageName) }
+        )
+        val mockAllowlist = mock<PermissionAllowlist> {
+            whenever(
+                getProductPrivilegedAppAllowlistState(packageName, PERMISSION_NAME_0)
+            ).thenReturn(allowlistState)
+        }
+        state.mutateExternalState().setPermissionAllowlist(mockAllowlist)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_nonRuntimeFlagsOnRuntimePermissions_getsCleared() {
+        val oldFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.PREGRANT or
+            PermissionFlags.RUNTIME_GRANTED
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.PREGRANT or PermissionFlags.RUNTIME_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " with existing $oldFlags flags, the actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_newPermissionsForPreM_requiresUserReview() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_DANGEROUS,
+            installedPackageTargetSdkVersion = Build.VERSION_CODES.LOLLIPOP,
+            isNewInstall = true
+        ) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " with no existing flags in pre M, actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_legacyOrImplicitGrantedPreviouslyRevoked_getsAppOpRevoked() {
+        val oldFlags = PermissionFlags.USER_FIXED
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_DANGEROUS,
+            installedPackageTargetSdkVersion = Build.VERSION_CODES.LOLLIPOP
+        ) {
+            setPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0, oldFlags)
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.USER_FIXED or
+            PermissionFlags.APP_OP_REVOKED
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " that should be LEGACY_GRANTED or IMPLICIT_GRANTED that was previously revoked," +
+                " the actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_legacyGrantedForPostM_userReviewRequirementRemoved() {
+        val oldFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " that used to require user review, the user review requirement should be removed" +
+                " if it's upgraded to post M. The actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_legacyGrantedPermissionsAlreadyReviewedForPostM_getsGranted() {
+        val oldFlags = PermissionFlags.LEGACY_GRANTED
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " that was already reviewed by the user, the permission should be RUNTIME_GRANTED" +
+                " if it's upgraded to post M. The actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_leanbackNotificationPermissionsForPostM_getsImplicitGranted() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_DANGEROUS,
+            permissionName = PERMISSION_POST_NOTIFICATIONS,
+            isNewInstall = true
+        ) {
+            oldState.mutateExternalState().setLeanback(true)
+        }
+
+        val actualFlags = getPermissionFlags(
+            APP_ID_1,
+            getUserIdEvaluated(),
+            PERMISSION_POST_NOTIFICATIONS
+        )
+        val expectedNewFlags = PermissionFlags.IMPLICIT_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime notification" +
+                " permission when isLeanback, the actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_implicitSourceFromNonRuntime_getsImplicitGranted() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_DANGEROUS,
+            implicitPermissions = setOf(PERMISSION_NAME_0),
+            isNewInstall = true
+        ) {
+            oldState.mutateExternalState().setImplicitToSourcePermissions(
+                MutableIndexedMap<String, IndexedListSet<String>>().apply {
+                    put(PERMISSION_NAME_0, MutableIndexedListSet<String>().apply {
+                        add(PERMISSION_NAME_1)
+                    })
+                }
+            )
+            addPermission(mockParsedPermission(PERMISSION_NAME_1, PACKAGE_NAME_0))
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.IMPLICIT_GRANTED or PermissionFlags.IMPLICIT
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime implicit" +
+                " permission that's source from a non-runtime permission, the actual permission" +
+                " flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    /**
+     * For a legacy granted or implicit permission during the app upgrade, when the permission
+     * should no longer be legacy or implicit granted, we want to remove the APP_OP_REVOKED flag
+     * so that the app can request the permission.
+     */
+    @Test
+    fun testEvaluatePermissionState_noLongerLegacyOrImplicitGranted_canBeRequested() {
+        val oldFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.APP_OP_REVOKED or
+            PermissionFlags.RUNTIME_GRANTED
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " that is no longer LEGACY_GRANTED or IMPLICIT_GRANTED, the actual permission" +
+                " flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_noLongerImplicit_getsRuntimeAndImplicitFlagsRemoved() {
+        val oldFlags = PermissionFlags.IMPLICIT or PermissionFlags.RUNTIME_GRANTED or
+            PermissionFlags.USER_SET or PermissionFlags.USER_FIXED
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " that is no longer implicit and we shouldn't retain as nearby device" +
+                " permissions, the actual permission flags $actualFlags should match the expected" +
+                " flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_noLongerImplicitNearbyWasGranted_getsRuntimeGranted() {
+        val oldFlags = PermissionFlags.IMPLICIT_GRANTED or PermissionFlags.IMPLICIT
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_DANGEROUS,
+            permissionName = PERMISSION_BLUETOOTH_CONNECT,
+            requestedPermissions = setOf(
+                PERMISSION_BLUETOOTH_CONNECT,
+                PERMISSION_ACCESS_BACKGROUND_LOCATION
+            )
+        ) {
+            setPermissionFlags(
+                APP_ID_1,
+                getUserIdEvaluated(),
+                PERMISSION_ACCESS_BACKGROUND_LOCATION,
+                PermissionFlags.RUNTIME_GRANTED
+            )
+        }
+
+        val actualFlags = getPermissionFlags(
+            APP_ID_1,
+            getUserIdEvaluated(),
+            PERMISSION_BLUETOOTH_CONNECT
+        )
+        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime nearby device" +
+                " permission that was granted by implicit, the actual permission flags" +
+                " $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_noLongerImplicitSystemOrPolicyFixedWasGranted_runtimeGranted() {
+        val oldFlags = PermissionFlags.IMPLICIT_GRANTED or PermissionFlags.IMPLICIT or
+            PermissionFlags.SYSTEM_FIXED
+        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.SYSTEM_FIXED
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime permission" +
+                " that was granted and is no longer implicit and is SYSTEM_FIXED or POLICY_FIXED," +
+                " the actual permission flags $actualFlags should match the expected" +
+                " flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_restrictedPermissionsNotExempt_getsRestrictionFlags() {
+        val oldFlags = PermissionFlags.RESTRICTION_REVOKED
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_DANGEROUS,
+            permissionInfoFlags = PermissionInfo.FLAG_HARD_RESTRICTED
+        ) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime hard" +
+                " restricted permission that is not exempted, the actual permission flags" +
+                " $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testEvaluatePermissionState_restrictedPermissionsIsExempted_clearsRestrictionFlags() {
+        val oldFlags = 0
+        testEvaluatePermissionState(
+            oldFlags,
+            PermissionInfo.PROTECTION_DANGEROUS,
+            permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED
+        ) {}
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.UPGRADE_EXEMPT
+        assertWithMessage(
+            "After $action is called for a package that requests a runtime soft" +
+                " restricted permission that is exempted, the actual permission flags" +
+                " $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testInheritImplicitPermissionStates_runtimeExistingImplicit_sourceFlagsNotInherited() {
+        val oldImplicitPermissionFlags = PermissionFlags.USER_FIXED
+        testInheritImplicitPermissionStates(
+            implicitPermissionFlags = oldImplicitPermissionFlags,
+            isNewInstallAndNewPermission = false
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = oldImplicitPermissionFlags or PermissionFlags.IMPLICIT_GRANTED or
+            PermissionFlags.APP_OP_REVOKED
+        assertWithMessage(
+            "After $action is called for a package that requests a permission that is" +
+                " implicit, existing and runtime, it should not inherit the runtime flags from" +
+                " the source permission. Hence the actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testInheritImplicitPermissionStates_nonRuntimeNewImplicit_sourceFlagsNotInherited() {
+        testInheritImplicitPermissionStates(
+            implicitPermissionProtectionLevel = PermissionInfo.PROTECTION_NORMAL
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a permission that is" +
+                " implicit, new and non-runtime, it should not inherit the runtime flags from" +
+                " the source permission. Hence the actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testInheritImplicitPermissionStates_runtimeNewImplicitPermissions_sourceFlagsInherited() {
+        val sourceRuntimeFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET
+        testInheritImplicitPermissionStates(sourceRuntimeFlags = sourceRuntimeFlags)
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = sourceRuntimeFlags or PermissionFlags.IMPLICIT_GRANTED or
+            PermissionFlags.IMPLICIT
+        assertWithMessage(
+            "After $action is called for a package that requests a permission that is" +
+                " implicit, new and runtime, it should inherit the runtime flags from" +
+                " the source permission. Hence the actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testInheritImplicitPermissionStates_grantingNewFromRevokeImplicit_onlyInheritFromSource() {
+        val sourceRuntimeFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET
+        testInheritImplicitPermissionStates(
+            implicitPermissionFlags = PermissionFlags.POLICY_FIXED,
+            sourceRuntimeFlags = sourceRuntimeFlags,
+            isAnySourcePermissionNonRuntime = false
+        )
+
+        val actualFlags = getPermissionFlags(APP_ID_1, getUserIdEvaluated(), PERMISSION_NAME_0)
+        val expectedNewFlags = sourceRuntimeFlags or PermissionFlags.IMPLICIT
+        assertWithMessage(
+            "After $action is called for a package that requests a permission that is" +
+                " implicit, existing, runtime and revoked, it should only inherit runtime flags" +
+                " from source permission. Hence the actual permission flags $actualFlags should" +
+                " match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    /**
+     * If it's a media implicit permission (one of RETAIN_IMPLICIT_FLAGS_PERMISSIONS), we want to
+     * remove the IMPLICIT flag so that they will be granted when they are no longer implicit.
+     * (instead of revoking it)
+     */
+    @Test
+    fun testInheritImplicitPermissionStates_mediaImplicitPermissions_getsImplicitFlagRemoved() {
+        val sourceRuntimeFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET
+        testInheritImplicitPermissionStates(
+            implicitPermissionName = PERMISSION_ACCESS_MEDIA_LOCATION,
+            sourceRuntimeFlags = sourceRuntimeFlags
+        )
+
+        val actualFlags = getPermissionFlags(
+            APP_ID_1,
+            getUserIdEvaluated(),
+            PERMISSION_ACCESS_MEDIA_LOCATION
+        )
+        val expectedNewFlags = sourceRuntimeFlags or PermissionFlags.IMPLICIT_GRANTED
+        assertWithMessage(
+            "After $action is called for a package that requests a media permission that" +
+                " is implicit, new and runtime, it should inherit the runtime flags from" +
+                " the source permission and have the IMPLICIT flag removed. Hence the actual" +
+                " permission flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    private fun testInheritImplicitPermissionStates(
+        implicitPermissionName: String = PERMISSION_NAME_0,
+        implicitPermissionFlags: Int = 0,
+        implicitPermissionProtectionLevel: Int = PermissionInfo.PROTECTION_DANGEROUS,
+        sourceRuntimeFlags: Int = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET,
+        isAnySourcePermissionNonRuntime: Boolean = true,
+        isNewInstallAndNewPermission: Boolean = true
+    ) {
+        val userId = getUserIdEvaluated()
+        val implicitPermission = mockParsedPermission(
+            implicitPermissionName,
+            PACKAGE_NAME_0,
+            protectionLevel = implicitPermissionProtectionLevel,
+        )
+        // For source from non-runtime in order to grant by implicit
+        val sourcePermission1 = mockParsedPermission(
+            PERMISSION_NAME_1,
+            PACKAGE_NAME_0,
+            protectionLevel = if (isAnySourcePermissionNonRuntime) {
+                PermissionInfo.PROTECTION_NORMAL
+            } else {
+                PermissionInfo.PROTECTION_DANGEROUS
+            }
+        )
+        // For inheriting runtime flags
+        val sourcePermission2 = mockParsedPermission(
+            PERMISSION_NAME_2,
+            PACKAGE_NAME_0,
+            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS,
+        )
+        val permissionOwnerPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(
+                PACKAGE_NAME_0,
+                permissions = listOf(implicitPermission, sourcePermission1, sourcePermission2)
+            )
+        )
+        val installedPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(
+                PACKAGE_NAME_1,
+                requestedPermissions = setOf(
+                    implicitPermissionName,
+                    PERMISSION_NAME_1,
+                    PERMISSION_NAME_2
+                ),
+                implicitPermissions = setOf(implicitPermissionName)
+            )
+        )
+        oldState.mutateExternalState().setImplicitToSourcePermissions(
+            MutableIndexedMap<String, IndexedListSet<String>>().apply {
+                put(implicitPermissionName, MutableIndexedListSet<String>().apply {
+                    add(PERMISSION_NAME_1)
+                    add(PERMISSION_NAME_2)
+                })
+            }
+        )
+        addPackageState(permissionOwnerPackageState)
+        addPermission(implicitPermission)
+        addPermission(sourcePermission1)
+        addPermission(sourcePermission2)
+        if (!isNewInstallAndNewPermission) {
+            addPackageState(installedPackageState)
+            setPermissionFlags(APP_ID_1, userId, implicitPermissionName, implicitPermissionFlags)
+        }
+        setPermissionFlags(APP_ID_1, userId, PERMISSION_NAME_2, sourceRuntimeFlags)
+
+        mutateState {
+            if (isNewInstallAndNewPermission) {
+                addPackageState(installedPackageState)
+                setPermissionFlags(
+                    APP_ID_1,
+                    userId,
+                    implicitPermissionName,
+                    implicitPermissionFlags,
+                    newState
+                )
+            }
+            testAction(installedPackageState)
+        }
+    }
+
+    /**
+     * Setup simple package states for testing evaluatePermissionState().
+     * permissionOwnerPackageState is definer of permissionName with APP_ID_0.
+     * installedPackageState is the installed package that requests permissionName with APP_ID_1.
+     *
+     * @param oldFlags the existing permission flags for APP_ID_1, userId, permissionName
+     * @param protectionLevel the protectionLevel for the permission
+     * @param permissionName the name of the permission (1) being defined (2) of the oldFlags, and
+     *                       (3) requested by installedPackageState
+     * @param requestedPermissions the permissions requested by installedPackageState
+     * @param implicitPermissions the implicit permissions of installedPackageState
+     * @param permissionInfoFlags the flags for the permission itself
+     * @param isInstalledPackageSystem whether installedPackageState is a system package
+     *
+     * @return installedPackageState
+     */
+    private fun testEvaluatePermissionState(
+        oldFlags: Int,
+        protectionLevel: Int,
+        permissionName: String = PERMISSION_NAME_0,
+        requestedPermissions: Set<String> = setOf(permissionName),
+        implicitPermissions: Set<String> = emptySet(),
+        permissionInfoFlags: Int = 0,
+        isInstalledPackageSystem: Boolean = false,
+        isInstalledPackagePrivileged: Boolean = false,
+        isInstalledPackageProduct: Boolean = false,
+        isInstalledPackageSignatureMatching: Boolean = false,
+        isInstalledPackageVendor: Boolean = false,
+        installedPackageTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+        isNewInstall: Boolean = false,
+        additionalSetup: () -> Unit
+    ) {
+        val userId = getUserIdEvaluated()
+        val parsedPermission = mockParsedPermission(
+            permissionName,
+            PACKAGE_NAME_0,
+            protectionLevel = protectionLevel,
+            flags = permissionInfoFlags
+        )
+        val permissionOwnerPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
+        )
+        val installedPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(
+                PACKAGE_NAME_1,
+                requestedPermissions = requestedPermissions,
+                implicitPermissions = implicitPermissions,
+                targetSdkVersion = installedPackageTargetSdkVersion,
+                isSignatureMatching = isInstalledPackageSignatureMatching
+            ),
+            isSystem = isInstalledPackageSystem,
+            isPrivileged = isInstalledPackagePrivileged,
+            isProduct = isInstalledPackageProduct,
+            isVendor = isInstalledPackageVendor
+        )
+        addPackageState(permissionOwnerPackageState)
+        if (!isNewInstall) {
+            addPackageState(installedPackageState)
+            setPermissionFlags(APP_ID_1, userId, permissionName, oldFlags)
+        }
+        addPermission(parsedPermission)
+
+        additionalSetup()
+
+        mutateState {
+            if (isNewInstall) {
+                addPackageState(installedPackageState, newState)
+                setPermissionFlags(APP_ID_1, userId, permissionName, oldFlags, newState)
+            }
+            testAction(installedPackageState)
+        }
+    }
+
+    private fun getUserIdEvaluated(): Int = when (action) {
+        Action.ON_USER_ADDED -> USER_ID_NEW
+        Action.ON_STORAGE_VOLUME_ADDED, Action.ON_PACKAGE_ADDED -> USER_ID_0
+    }
+
+    private fun MutateStateScope.testAction(packageState: PackageState) {
+        with(appIdPermissionPolicy) {
+            when (action) {
+                Action.ON_USER_ADDED -> onUserAdded(getUserIdEvaluated())
+                Action.ON_STORAGE_VOLUME_ADDED -> onStorageVolumeMounted(
+                    null,
+                    listOf(packageState.packageName),
+                    true
+                )
+                Action.ON_PACKAGE_ADDED -> onPackageAdded(packageState)
+            }
+        }
+    }
+
+    enum class Action { ON_USER_ADDED, ON_STORAGE_VOLUME_ADDED, ON_PACKAGE_ADDED }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun data(): Array<Action> = Action.values()
+    }
+}
\ No newline at end of file
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
deleted file mode 100644
index 3cf57a3cf57a2044ecbab254a4b780b1fff8d2e4..0000000000000000000000000000000000000000
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
+++ /dev/null
@@ -1,1937 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.permission.test
-
-import android.Manifest
-import android.content.pm.PackageManager
-import android.content.pm.PermissionGroupInfo
-import android.content.pm.PermissionInfo
-import android.content.pm.SigningDetails
-import android.os.Build
-import android.os.Bundle
-import android.util.ArrayMap
-import android.util.SparseArray
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.modules.utils.testing.ExtendedMockitoRule
-import com.android.server.extendedtestutils.wheneverStatic
-import com.android.server.permission.access.MutableAccessState
-import com.android.server.permission.access.MutableUserState
-import com.android.server.permission.access.MutateStateScope
-import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
-import com.android.server.permission.access.permission.AppIdPermissionPolicy
-import com.android.server.permission.access.permission.Permission
-import com.android.server.permission.access.permission.PermissionFlags
-import com.android.server.permission.access.util.hasBits
-import com.android.server.pm.parsing.PackageInfoUtils
-import com.android.server.pm.permission.PermissionAllowlist
-import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.pkg.PackageState
-import com.android.server.pm.pkg.PackageUserState
-import com.android.server.pm.pkg.component.ParsedPermission
-import com.android.server.pm.pkg.component.ParsedPermissionGroup
-import com.android.server.testutils.any
-import com.android.server.testutils.mock
-import com.android.server.testutils.whenever
-import com.google.common.truth.Truth.assertWithMessage
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyLong
-
-/**
- * Mocking unit test for AppIdPermissionPolicy.
- */
-@RunWith(AndroidJUnit4::class)
-class AppIdPermissionPolicyTest {
-    private lateinit var oldState: MutableAccessState
-    private lateinit var newState: MutableAccessState
-
-    private val defaultPermissionGroup = mockParsedPermissionGroup(
-        PERMISSION_GROUP_NAME_0,
-        PACKAGE_NAME_0
-    )
-    private val defaultPermissionTree = mockParsedPermission(
-        PERMISSION_TREE_NAME,
-        PACKAGE_NAME_0,
-        isTree = true
-    )
-    private val defaultPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0)
-
-    private val appIdPermissionPolicy = AppIdPermissionPolicy()
-
-    @Rule
-    @JvmField
-    val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
-        .spyStatic(PackageInfoUtils::class.java)
-        .build()
-
-    @Before
-    fun setUp() {
-        oldState = MutableAccessState()
-        createUserState(USER_ID_0)
-        oldState.mutateExternalState().setPackageStates(ArrayMap())
-        oldState.mutateExternalState().setDisabledSystemPackageStates(ArrayMap())
-        mockPackageInfoUtilsGeneratePermissionInfo()
-        mockPackageInfoUtilsGeneratePermissionGroupInfo()
-    }
-
-    private fun createUserState(userId: Int) {
-        oldState.mutateExternalState().mutateUserIds().add(userId)
-        oldState.mutateUserStatesNoWrite().put(userId, MutableUserState())
-    }
-
-    private fun mockPackageInfoUtilsGeneratePermissionInfo() {
-        wheneverStatic {
-            PackageInfoUtils.generatePermissionInfo(any(ParsedPermission::class.java), anyLong())
-        }.thenAnswer { invocation ->
-            val parsedPermission = invocation.getArgument<ParsedPermission>(0)
-            val generateFlags = invocation.getArgument<Long>(1)
-            PermissionInfo(parsedPermission.backgroundPermission).apply {
-                name = parsedPermission.name
-                packageName = parsedPermission.packageName
-                metaData = if (generateFlags.toInt().hasBits(PackageManager.GET_META_DATA)) {
-                    parsedPermission.metaData
-                } else {
-                    null
-                }
-                @Suppress("DEPRECATION")
-                protectionLevel = parsedPermission.protectionLevel
-                group = parsedPermission.group
-                flags = parsedPermission.flags
-            }
-        }
-    }
-
-    private fun mockPackageInfoUtilsGeneratePermissionGroupInfo() {
-        wheneverStatic {
-            PackageInfoUtils.generatePermissionGroupInfo(
-                any(ParsedPermissionGroup::class.java),
-                anyLong()
-            )
-        }.thenAnswer { invocation ->
-            val parsedPermissionGroup = invocation.getArgument<ParsedPermissionGroup>(0)
-            val generateFlags = invocation.getArgument<Long>(1)
-            @Suppress("DEPRECATION")
-            PermissionGroupInfo().apply {
-                name = parsedPermissionGroup.name
-                packageName = parsedPermissionGroup.packageName
-                metaData = if (generateFlags.toInt().hasBits(PackageManager.GET_META_DATA)) {
-                    parsedPermissionGroup.metaData
-                } else {
-                    null
-                }
-                flags = parsedPermissionGroup.flags
-            }
-        }
-    }
-
-    @Test
-    fun testResetRuntimePermissions_runtimeGranted_getsRevoked() {
-        val oldFlags = PermissionFlags.RUNTIME_GRANTED
-        val expectedNewFlags = 0
-        testResetRuntimePermissions(oldFlags, expectedNewFlags)
-    }
-
-    @Test
-    fun testResetRuntimePermissions_roleGranted_getsGranted() {
-        val oldFlags = PermissionFlags.ROLE
-        val expectedNewFlags = PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED
-        testResetRuntimePermissions(oldFlags, expectedNewFlags)
-    }
-
-    @Test
-    fun testResetRuntimePermissions_nullAndroidPackage_remainsUnchanged() {
-        val oldFlags = PermissionFlags.RUNTIME_GRANTED
-        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED
-        testResetRuntimePermissions(oldFlags, expectedNewFlags, isAndroidPackageMissing = true)
-    }
-
-    private fun testResetRuntimePermissions(
-        oldFlags: Int,
-        expectedNewFlags: Int,
-        isAndroidPackageMissing: Boolean = false
-    ) {
-        val parsedPermission = mockParsedPermission(
-            PERMISSION_NAME_0,
-            PACKAGE_NAME_0,
-            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS,
-        )
-        val permissionOwnerPackageState = mockPackageState(
-            APP_ID_0,
-            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
-        )
-        val requestingPackageState = if (isAndroidPackageMissing) {
-            mockPackageState(APP_ID_1, PACKAGE_NAME_1)
-        } else {
-            mockPackageState(
-                APP_ID_1,
-                mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
-            )
-        }
-        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
-        addPackageState(permissionOwnerPackageState)
-        addPackageState(requestingPackageState)
-        addPermission(parsedPermission)
-
-        mutateState {
-            with(appIdPermissionPolicy) {
-                resetRuntimePermissions(PACKAGE_NAME_1, USER_ID_0)
-            }
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        assertWithMessage(
-            "After resetting runtime permissions, permission flags did not match" +
-                " expected values: expectedNewFlags is $expectedNewFlags," +
-                " actualFlags is $actualFlags, while the oldFlags is $oldFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionsOfMissingSystemApp_getsAdopted() {
-        testAdoptPermissions(hasMissingPackage = true, isSystem = true)
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a null adopt permission package," +
-                " the permission package name: ${getPermission(PERMISSION_NAME_0)?.packageName}" +
-                " did not match the expected package name: $PACKAGE_NAME_0"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_0)
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionsOfExistingSystemApp_notAdopted() {
-        testAdoptPermissions(isSystem = true)
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a non-null adopt permission" +
-                " package, the permission package name:" +
-                " ${getPermission(PERMISSION_NAME_0)?.packageName} should not match the" +
-                " package name: $PACKAGE_NAME_0"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.packageName)
-            .isNotEqualTo(PACKAGE_NAME_0)
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionsOfNonSystemApp_notAdopted() {
-        testAdoptPermissions(hasMissingPackage = true)
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a non-system adopt permission" +
-                " package, the permission package name:" +
-                " ${getPermission(PERMISSION_NAME_0)?.packageName} should not match the" +
-                " package name: $PACKAGE_NAME_0"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.packageName)
-            .isNotEqualTo(PACKAGE_NAME_0)
-    }
-
-    private fun testAdoptPermissions(
-        hasMissingPackage: Boolean = false,
-        isSystem: Boolean = false
-    ) {
-        val parsedPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_1)
-        val packageToAdoptPermission = if (hasMissingPackage) {
-            mockPackageState(APP_ID_1, PACKAGE_NAME_1, isSystem = isSystem)
-        } else {
-            mockPackageState(
-                APP_ID_1,
-                mockAndroidPackage(
-                    PACKAGE_NAME_1,
-                    permissions = listOf(parsedPermission)
-                ),
-                isSystem = isSystem
-            )
-        }
-        addPackageState(packageToAdoptPermission)
-        addPermission(parsedPermission)
-
-        mutateState {
-            val installedPackage = mockPackageState(
-                APP_ID_0,
-                mockAndroidPackage(
-                    PACKAGE_NAME_0,
-                    permissions = listOf(defaultPermission),
-                    adoptPermissions = listOf(PACKAGE_NAME_1)
-                )
-            )
-            addPackageState(installedPackage, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(installedPackage)
-            }
-        }
-    }
-
-    @Test
-    fun testOnPackageAdded_newPermissionGroup_getsDeclared() {
-        mutateState {
-            val packageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
-            addPackageState(packageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(packageState)
-            }
-        }
-
-        assertWithMessage(
-            "After onPackageAdded() is called when there is no existing" +
-                " permission groups, the new permission group $PERMISSION_GROUP_NAME_0 is not added"
-        )
-            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.name)
-            .isEqualTo(PERMISSION_GROUP_NAME_0)
-    }
-
-    @Test
-    fun testOnPackageAdded_systemAppTakingOverPermissionGroupDefinition_getsTakenOver() {
-        testTakingOverPermissionAndPermissionGroupDefinitions(newPermissionOwnerIsSystem = true)
-
-        assertWithMessage(
-            "After onPackageAdded() is called when $PERMISSION_GROUP_NAME_0 already" +
-                " exists in the system, the system app $PACKAGE_NAME_0 didn't takeover the" +
-                " ownership of this permission group"
-        )
-            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_0)
-    }
-
-    @Test
-    fun testOnPackageAdded_instantApps_remainsUnchanged() {
-        testTakingOverPermissionAndPermissionGroupDefinitions(
-            newPermissionOwnerIsInstant = true,
-            permissionGroupAlreadyExists = false
-        )
-
-        assertWithMessage(
-            "After onPackageAdded() is called for an instant app," +
-                " the new permission group $PERMISSION_GROUP_NAME_0 should not be added"
-        )
-            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0))
-            .isNull()
-    }
-
-    @Test
-    fun testOnPackageAdded_nonSystemAppTakingOverPermissionGroupDefinition_remainsUnchanged() {
-        testTakingOverPermissionAndPermissionGroupDefinitions()
-
-        assertWithMessage(
-            "After onPackageAdded() is called when $PERMISSION_GROUP_NAME_0 already" +
-                " exists in the system, non-system app $PACKAGE_NAME_0 shouldn't takeover" +
-                " ownership of this permission group"
-        )
-            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_1)
-    }
-
-    @Test
-    fun testOnPackageAdded_takingOverPermissionGroupDeclaredBySystemApp_remainsUnchanged() {
-        testTakingOverPermissionAndPermissionGroupDefinitions(oldPermissionOwnerIsSystem = true)
-
-        assertWithMessage(
-            "After onPackageAdded() is called when $PERMISSION_GROUP_NAME_0 already" +
-                " exists in the system and is owned by a system app, app $PACKAGE_NAME_0" +
-                " shouldn't takeover ownership of this permission group"
-        )
-            .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_1)
-    }
-
-    @Test
-    fun testOnPackageAdded_newPermission_getsDeclared() {
-        mutateState {
-            val packageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
-            addPackageState(packageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(packageState)
-            }
-        }
-
-        assertWithMessage(
-            "After onPackageAdded() is called when there is no existing" +
-                " permissions, the new permission $PERMISSION_NAME_0 is not added"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.name)
-            .isEqualTo(PERMISSION_NAME_0)
-    }
-
-    @Test
-    fun testOnPackageAdded_configPermission_getsTakenOver() {
-        testTakingOverPermissionAndPermissionGroupDefinitions(
-            oldPermissionOwnerIsSystem = true,
-            newPermissionOwnerIsSystem = true,
-            type = Permission.TYPE_CONFIG,
-            isReconciled = false
-        )
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a config permission with" +
-                " no owner, the ownership is not taken over by a system app $PACKAGE_NAME_0"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_0)
-    }
-
-    @Test
-    fun testOnPackageAdded_systemAppTakingOverPermissionDefinition_getsTakenOver() {
-        testTakingOverPermissionAndPermissionGroupDefinitions(newPermissionOwnerIsSystem = true)
-
-        assertWithMessage(
-            "After onPackageAdded() is called when $PERMISSION_NAME_0 already" +
-                " exists in the system, the system app $PACKAGE_NAME_0 didn't takeover ownership" +
-                " of this permission"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_0)
-    }
-
-    @Test
-    fun testOnPackageAdded_nonSystemAppTakingOverPermissionDefinition_remainsUnchanged() {
-        testTakingOverPermissionAndPermissionGroupDefinitions()
-
-        assertWithMessage(
-            "After onPackageAdded() is called when $PERMISSION_NAME_0 already" +
-                " exists in the system, the non-system app $PACKAGE_NAME_0 shouldn't takeover" +
-                " ownership of this permission"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_1)
-    }
-
-    @Test
-    fun testOnPackageAdded_takingOverPermissionDeclaredBySystemApp_remainsUnchanged() {
-        testTakingOverPermissionAndPermissionGroupDefinitions(oldPermissionOwnerIsSystem = true)
-
-        assertWithMessage(
-            "After onPackageAdded() is called when $PERMISSION_NAME_0 already" +
-                " exists in system and is owned by a system app, the $PACKAGE_NAME_0 shouldn't" +
-                " takeover ownership of this permission"
-        )
-            .that(getPermission(PERMISSION_NAME_0)?.packageName)
-            .isEqualTo(PACKAGE_NAME_1)
-    }
-
-    private fun testTakingOverPermissionAndPermissionGroupDefinitions(
-        oldPermissionOwnerIsSystem: Boolean = false,
-        newPermissionOwnerIsSystem: Boolean = false,
-        newPermissionOwnerIsInstant: Boolean = false,
-        permissionGroupAlreadyExists: Boolean = true,
-        permissionAlreadyExists: Boolean = true,
-        type: Int = Permission.TYPE_MANIFEST,
-        isReconciled: Boolean = true,
-    ) {
-        val oldPermissionOwnerPackageState = mockPackageState(
-            APP_ID_1,
-            PACKAGE_NAME_1,
-            isSystem = oldPermissionOwnerIsSystem
-        )
-        addPackageState(oldPermissionOwnerPackageState)
-        if (permissionGroupAlreadyExists) {
-            addPermissionGroup(mockParsedPermissionGroup(PERMISSION_GROUP_NAME_0, PACKAGE_NAME_1))
-        }
-        if (permissionAlreadyExists) {
-            addPermission(
-                mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_1),
-                type = type,
-                isReconciled = isReconciled
-            )
-        }
-
-        mutateState {
-            val newPermissionOwnerPackageState = mockPackageState(
-                APP_ID_0,
-                mockSimpleAndroidPackage(),
-                isSystem = newPermissionOwnerIsSystem,
-                isInstantApp = newPermissionOwnerIsInstant
-            )
-            addPackageState(newPermissionOwnerPackageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(newPermissionOwnerPackageState)
-            }
-        }
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionGroupChanged_getsRevoked() {
-        testPermissionChanged(
-            oldPermissionGroup = PERMISSION_GROUP_NAME_1,
-            newPermissionGroup = PERMISSION_GROUP_NAME_0
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that has a permission group change" +
-                " for a permission it defines, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_protectionLevelChanged_getsRevoked() {
-        testPermissionChanged(newProtectionLevel = PermissionInfo.PROTECTION_INTERNAL)
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that has a protection level change" +
-                " for a permission it defines, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    private fun testPermissionChanged(
-        oldPermissionGroup: String? = null,
-        newPermissionGroup: String? = null,
-        newProtectionLevel: Int = PermissionInfo.PROTECTION_DANGEROUS
-    ) {
-        val oldPermission = mockParsedPermission(
-            PERMISSION_NAME_0,
-            PACKAGE_NAME_0,
-            group = oldPermissionGroup,
-            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS
-        )
-        val oldPackageState = mockPackageState(
-            APP_ID_0,
-            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(oldPermission))
-        )
-        addPackageState(oldPackageState)
-        addPermission(oldPermission)
-        setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED)
-
-        mutateState {
-            val newPermission = mockParsedPermission(
-                PERMISSION_NAME_0,
-                PACKAGE_NAME_0,
-                group = newPermissionGroup,
-                protectionLevel = newProtectionLevel
-            )
-            val newPackageState = mockPackageState(
-                APP_ID_0,
-                mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(newPermission))
-            )
-            addPackageState(newPackageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(newPackageState)
-            }
-        }
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionTreeNoLongerDeclared_getsDefinitionRemoved() {
-        testPermissionDeclaration {}
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that no longer defines a permission" +
-                " tree, the permission tree: $PERMISSION_NAME_0 in system state should be removed"
-        )
-            .that(getPermissionTree(PERMISSION_NAME_0))
-            .isNull()
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionTreeByDisabledSystemPackage_remainsUnchanged() {
-        testPermissionDeclaration {
-            val disabledSystemPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
-            addDisabledSystemPackageState(disabledSystemPackageState)
-        }
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that no longer defines" +
-                " a permission tree while this permission tree is still defined by" +
-                " a disabled system package, the permission tree: $PERMISSION_NAME_0 in" +
-                " system state should not be removed"
-        )
-            .that(getPermissionTree(PERMISSION_TREE_NAME))
-            .isNotNull()
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionNoLongerDeclared_getsDefinitionRemoved() {
-        testPermissionDeclaration {}
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that no longer defines a permission," +
-                " the permission: $PERMISSION_NAME_0 in system state should be removed"
-        )
-            .that(getPermission(PERMISSION_NAME_0))
-            .isNull()
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionByDisabledSystemPackage_remainsUnchanged() {
-        testPermissionDeclaration {
-            val disabledSystemPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
-            addDisabledSystemPackageState(disabledSystemPackageState)
-        }
-
-        assertWithMessage(
-            "After onPackageAdded() is called for a disabled system package and it's updated apk" +
-                " no longer defines a permission, the permission: $PERMISSION_NAME_0 in" +
-                " system state should not be removed"
-        )
-            .that(getPermission(PERMISSION_NAME_0))
-            .isNotNull()
-    }
-
-    private fun testPermissionDeclaration(additionalSetup: () -> Unit) {
-        val oldPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
-        addPackageState(oldPackageState)
-        addPermission(defaultPermissionTree)
-        addPermission(defaultPermission)
-
-        additionalSetup()
-
-        mutateState {
-            val newPackageState = mockPackageState(APP_ID_0, mockAndroidPackage(PACKAGE_NAME_0))
-            addPackageState(newPackageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(newPackageState)
-            }
-        }
-    }
-
-    @Test
-    fun testOnPackageAdded_permissionsNoLongerRequested_getsFlagsRevoked() {
-        val parsedPermission = mockParsedPermission(
-            PERMISSION_NAME_0,
-            PACKAGE_NAME_0,
-            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS
-        )
-        val oldPackageState = mockPackageState(
-            APP_ID_0,
-            mockAndroidPackage(
-                PACKAGE_NAME_0,
-                permissions = listOf(parsedPermission),
-                requestedPermissions = setOf(PERMISSION_NAME_0)
-            )
-        )
-        addPackageState(oldPackageState)
-        addPermission(parsedPermission)
-        setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED)
-
-        mutateState {
-            val newPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
-            addPackageState(newPackageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(newPackageState)
-            }
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that no longer requests a permission" +
-                " the actual permission flags $actualFlags should match the" +
-                " expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_storageAndMediaPermissionsDowngradingPastQ_getsRuntimeRevoked() {
-        testRevokePermissionsOnPackageUpdate(
-            PermissionFlags.RUNTIME_GRANTED,
-            newTargetSdkVersion = Build.VERSION_CODES.P
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that's downgrading past Q" +
-                " the actual permission flags $actualFlags should match the" +
-                " expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_storageAndMediaPermissionsNotDowngradingPastQ_remainsUnchanged() {
-        val oldFlags = PermissionFlags.RUNTIME_GRANTED
-        testRevokePermissionsOnPackageUpdate(
-            oldFlags,
-            oldTargetSdkVersion = Build.VERSION_CODES.P,
-            newTargetSdkVersion = Build.VERSION_CODES.P
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that's not downgrading past Q" +
-                " the actual permission flags $actualFlags should match the" +
-                " expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_policyFixedDowngradingPastQ_remainsUnchanged() {
-        val oldFlags = PermissionFlags.RUNTIME_GRANTED and PermissionFlags.POLICY_FIXED
-        testRevokePermissionsOnPackageUpdate(oldFlags, newTargetSdkVersion = Build.VERSION_CODES.P)
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that's downgrading past Q" +
-                " the actual permission flags with PermissionFlags.POLICY_FIXED $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_newlyRequestingLegacyExternalStorage_getsRuntimeRevoked() {
-        testRevokePermissionsOnPackageUpdate(
-            PermissionFlags.RUNTIME_GRANTED,
-            oldTargetSdkVersion = Build.VERSION_CODES.P,
-            newTargetSdkVersion = Build.VERSION_CODES.P,
-            oldIsRequestLegacyExternalStorage = false
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package with" +
-                " newlyRequestingLegacyExternalStorage, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_missingOldPackage_remainsUnchanged() {
-        val oldFlags = PermissionFlags.RUNTIME_GRANTED
-        testRevokePermissionsOnPackageUpdate(
-            oldFlags,
-            newTargetSdkVersion = Build.VERSION_CODES.P,
-            isOldPackageMissing = true
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that's downgrading past Q" +
-                " and doesn't have the oldPackage, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    private fun testRevokePermissionsOnPackageUpdate(
-        oldFlags: Int,
-        oldTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
-        newTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
-        oldIsRequestLegacyExternalStorage: Boolean = true,
-        newIsRequestLegacyExternalStorage: Boolean = true,
-        isOldPackageMissing: Boolean = false
-    ) {
-        val parsedPermission = mockParsedPermission(
-            PERMISSION_READ_EXTERNAL_STORAGE,
-            PACKAGE_NAME_0,
-            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS
-        )
-        val oldPackageState = if (isOldPackageMissing) {
-            mockPackageState(APP_ID_0, PACKAGE_NAME_0)
-        } else {
-            mockPackageState(
-                APP_ID_0,
-                mockAndroidPackage(
-                    PACKAGE_NAME_0,
-                    targetSdkVersion = oldTargetSdkVersion,
-                    isRequestLegacyExternalStorage = oldIsRequestLegacyExternalStorage,
-                    requestedPermissions = setOf(PERMISSION_READ_EXTERNAL_STORAGE),
-                    permissions = listOf(parsedPermission)
-                )
-            )
-        }
-        addPackageState(oldPackageState)
-        addPermission(parsedPermission)
-        setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE, oldFlags)
-
-        mutateState {
-            val newPackageState = mockPackageState(
-                APP_ID_0,
-                mockAndroidPackage(
-                    PACKAGE_NAME_0,
-                    targetSdkVersion = newTargetSdkVersion,
-                    isRequestLegacyExternalStorage = newIsRequestLegacyExternalStorage,
-                    requestedPermissions = setOf(PERMISSION_READ_EXTERNAL_STORAGE),
-                    permissions = listOf(parsedPermission)
-                )
-            )
-            addPackageState(newPackageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(newPackageState)
-            }
-        }
-    }
-
-    @Test
-    fun testOnPackageAdded_normalPermissionAlreadyGranted_remainsUnchanged() {
-        val oldFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.INSTALL_REVOKED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_NORMAL) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a normal permission" +
-                " with an existing INSTALL_GRANTED flag, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_normalPermissionNotInstallRevoked_getsGranted() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_NORMAL,
-            isNewInstall = true
-        ) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a normal permission" +
-                " with no existing flags, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_normalPermissionRequestedByInstalledPackage_getsGranted() {
-        val oldFlags = PermissionFlags.INSTALL_REVOKED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_NORMAL) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a normal permission" +
-                " with the INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags since it's a new install"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    /**
-     * We setup a permission protection level change from SIGNATURE to NORMAL in order to make
-     * the permission a "changed permission" in order to test evaluatePermissionState() called by
-     * evaluatePermissionStateForAllPackages(). This makes the requestingPackageState not the
-     * installedPackageState so that we can test whether requesting by system package will give us
-     * the expected permission flags.
-     *
-     * Besides, this also helps us test evaluatePermissionStateForAllPackages(). Since both
-     * evaluatePermissionStateForAllPackages() and evaluateAllPermissionStatesForPackage() call
-     * evaluatePermissionState() in their implementations, we use these tests as the only tests
-     * that test evaluatePermissionStateForAllPackages()
-     */
-    @Test
-    fun testOnPackageAdded_normalPermissionRequestedBySystemPackage_getsGranted() {
-        testEvaluateNormalPermissionStateWithPermissionChanges(requestingPackageIsSystem = true)
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a system package that requests a normal" +
-                " permission with INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_normalCompatibilityPermission_getsGranted() {
-        testEvaluateNormalPermissionStateWithPermissionChanges(
-            permissionName = PERMISSION_POST_NOTIFICATIONS,
-            requestingPackageTargetSdkVersion = Build.VERSION_CODES.S
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_POST_NOTIFICATIONS)
-        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a normal compatibility" +
-                " permission with INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_normalPermissionPreviouslyRevoked_getsInstallRevoked() {
-        testEvaluateNormalPermissionStateWithPermissionChanges()
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.INSTALL_REVOKED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a normal" +
-                " permission with INSTALL_REVOKED flag, the actual permission flags $actualFlags" +
-                " should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    private fun testEvaluateNormalPermissionStateWithPermissionChanges(
-        permissionName: String = PERMISSION_NAME_0,
-        requestingPackageTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
-        requestingPackageIsSystem: Boolean = false
-    ) {
-        val oldParsedPermission = mockParsedPermission(
-            permissionName,
-            PACKAGE_NAME_0,
-            protectionLevel = PermissionInfo.PROTECTION_SIGNATURE
-        )
-        val oldPermissionOwnerPackageState = mockPackageState(
-            APP_ID_0,
-            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(oldParsedPermission))
-        )
-        val requestingPackageState = mockPackageState(
-            APP_ID_1,
-            mockAndroidPackage(
-                PACKAGE_NAME_1,
-                requestedPermissions = setOf(permissionName),
-                targetSdkVersion = requestingPackageTargetSdkVersion
-            ),
-            isSystem = requestingPackageIsSystem,
-        )
-        addPackageState(oldPermissionOwnerPackageState)
-        addPackageState(requestingPackageState)
-        addPermission(oldParsedPermission)
-        val oldFlags = PermissionFlags.INSTALL_REVOKED
-        setPermissionFlags(APP_ID_1, USER_ID_0, permissionName, oldFlags)
-
-        mutateState {
-            val newPermissionOwnerPackageState = mockPackageState(
-                APP_ID_0,
-                mockAndroidPackage(
-                    PACKAGE_NAME_0,
-                    permissions = listOf(mockParsedPermission(permissionName, PACKAGE_NAME_0))
-                )
-            )
-            addPackageState(newPermissionOwnerPackageState, newState)
-            with(appIdPermissionPolicy) {
-                onPackageAdded(newPermissionOwnerPackageState)
-            }
-        }
-    }
-
-    @Test
-    fun testOnPackageAdded_normalAppOpPermission_getsRoleAndUserSetFlagsPreserved() {
-        val oldFlags = PermissionFlags.ROLE or PermissionFlags.USER_SET
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_NORMAL or PermissionInfo.PROTECTION_FLAG_APPOP
-        ) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED or oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a normal app op" +
-                " permission with existing ROLE and USER_SET flags, the actual permission flags" +
-                " $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_internalPermissionWasGrantedWithMissingPackage_getsProtectionGranted() {
-        val oldFlags = PermissionFlags.PROTECTION_GRANTED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_INTERNAL) {
-            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
-            addPackageState(packageStateWithMissingPackage)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests an internal permission" +
-                " with missing android package and $oldFlags flag, the actual permission flags" +
-                " $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_internalAppOpPermission_getsRoleAndUserSetFlagsPreserved() {
-        val oldFlags = PermissionFlags.PROTECTION_GRANTED or PermissionFlags.ROLE or
-            PermissionFlags.USER_SET
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_INTERNAL or PermissionInfo.PROTECTION_FLAG_APPOP
-        ) {
-            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
-            addPackageState(packageStateWithMissingPackage)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests an internal permission" +
-                " with missing android package and $oldFlags flag and the permission isAppOp," +
-                " the actual permission flags $actualFlags should match the expected" +
-                " flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_internalDevelopmentPermission_getsRuntimeGrantedPreserved() {
-        val oldFlags = PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_INTERNAL or PermissionInfo.PROTECTION_FLAG_DEVELOPMENT
-        ) {
-            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
-            addPackageState(packageStateWithMissingPackage)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests an internal permission" +
-                " with missing android package and $oldFlags flag and permission isDevelopment," +
-                " the actual permission flags $actualFlags should match the expected" +
-                " flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_internalRolePermission_getsRoleAndRuntimeGrantedPreserved() {
-        val oldFlags = PermissionFlags.PROTECTION_GRANTED or PermissionFlags.ROLE or
-            PermissionFlags.RUNTIME_GRANTED
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_INTERNAL or PermissionInfo.PROTECTION_FLAG_ROLE
-        ) {
-            val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE)
-            addPackageState(packageStateWithMissingPackage)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests an internal permission" +
-                " with missing android package and $oldFlags flag and the permission isRole," +
-                " the actual permission flags $actualFlags should match the expected" +
-                " flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_signaturePrivilegedPermissionNotAllowlisted_isNotGranted() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_SIGNATURE or PermissionInfo.PROTECTION_FLAG_PRIVILEGED,
-            isInstalledPackageSystem = true,
-            isInstalledPackagePrivileged = true,
-            isInstalledPackageProduct = true,
-            // To mock the return value of shouldGrantPrivilegedOrOemPermission()
-            isInstalledPackageVendor = true,
-            isNewInstall = true
-        ) {
-            val platformPackage = mockPackageState(
-                PLATFORM_APP_ID,
-                mockAndroidPackage(PLATFORM_PACKAGE_NAME)
-            )
-            setupAllowlist(PACKAGE_NAME_1, false)
-            addPackageState(platformPackage)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a signature privileged" +
-                " permission that's not allowlisted, the actual permission" +
-                " flags $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_nonPrivilegedPermissionShouldGrantBySignature_getsProtectionGranted() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_SIGNATURE,
-            isInstalledPackageSystem = true,
-            isInstalledPackagePrivileged = true,
-            isInstalledPackageProduct = true,
-            isInstalledPackageSignatureMatching = true,
-            isInstalledPackageVendor = true,
-            isNewInstall = true
-        ) {
-            val platformPackage = mockPackageState(
-                PLATFORM_APP_ID,
-                mockAndroidPackage(PLATFORM_PACKAGE_NAME, isSignatureMatching = true)
-            )
-            setupAllowlist(PACKAGE_NAME_1, false)
-            addPackageState(platformPackage)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.PROTECTION_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a signature" +
-                " non-privileged permission, the actual permission" +
-                " flags $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_privilegedAllowlistPermissionShouldGrantByProtectionFlags_getsGranted() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_SIGNATURE or PermissionInfo.PROTECTION_FLAG_PRIVILEGED,
-            isInstalledPackageSystem = true,
-            isInstalledPackagePrivileged = true,
-            isInstalledPackageProduct = true,
-            isNewInstall = true
-        ) {
-            val platformPackage = mockPackageState(
-                PLATFORM_APP_ID,
-                mockAndroidPackage(PLATFORM_PACKAGE_NAME)
-            )
-            setupAllowlist(PACKAGE_NAME_1, true)
-            addPackageState(platformPackage)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.PROTECTION_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a signature privileged" +
-                " permission that's allowlisted and should grant by protection flags, the actual" +
-                " permission flags $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    private fun setupAllowlist(
-        packageName: String,
-        allowlistState: Boolean,
-        state: MutableAccessState = oldState
-    ) {
-        state.mutateExternalState().setPrivilegedPermissionAllowlistPackages(
-            MutableIndexedListSet<String>().apply { add(packageName) }
-        )
-        val mockAllowlist = mock<PermissionAllowlist> {
-            whenever(
-                getProductPrivilegedAppAllowlistState(packageName, PERMISSION_NAME_0)
-            ).thenReturn(allowlistState)
-        }
-        state.mutateExternalState().setPermissionAllowlist(mockAllowlist)
-    }
-
-    @Test
-    fun testOnPackageAdded_nonRuntimeFlagsOnRuntimePermissions_getsCleared() {
-        val oldFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.PREGRANT or
-            PermissionFlags.RUNTIME_GRANTED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.PREGRANT or PermissionFlags.RUNTIME_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " with existing $oldFlags flags, the actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_newPermissionsForPreM_requiresUserReview() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_DANGEROUS,
-            installedPackageTargetSdkVersion = Build.VERSION_CODES.LOLLIPOP,
-            isNewInstall = true
-        ) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " with no existing flags in pre M, actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_legacyOrImplicitGrantedPermissionPreviouslyRevoked_getsAppOpRevoked() {
-        val oldFlags = PermissionFlags.USER_FIXED
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_DANGEROUS,
-            installedPackageTargetSdkVersion = Build.VERSION_CODES.LOLLIPOP
-        ) {
-            setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.USER_FIXED or
-            PermissionFlags.APP_OP_REVOKED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " that should be LEGACY_GRANTED or IMPLICIT_GRANTED that was previously revoked," +
-                " the actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_legacyGrantedPermissionsForPostM_userReviewRequirementRemoved() {
-        val oldFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " that used to require user review, the user review requirement should be removed" +
-                " if it's upgraded to post M. The actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_legacyGrantedPermissionsAlreadyReviewedForPostM_getsGranted() {
-        val oldFlags = PermissionFlags.LEGACY_GRANTED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " that was already reviewed by the user, the permission should be RUNTIME_GRANTED" +
-                " if it's upgraded to post M. The actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_leanbackNotificationPermissionsForPostM_getsImplicitGranted() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_DANGEROUS,
-            permissionName = PERMISSION_POST_NOTIFICATIONS,
-            isNewInstall = true
-        ) {
-            oldState.mutateExternalState().setLeanback(true)
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_POST_NOTIFICATIONS)
-        val expectedNewFlags = PermissionFlags.IMPLICIT_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime notification" +
-                " permission when isLeanback, the actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_implicitSourceFromNonRuntime_getsImplicitGranted() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_DANGEROUS,
-            implicitPermissions = setOf(PERMISSION_NAME_0),
-            isNewInstall = true
-        ) {
-            oldState.mutateExternalState().setImplicitToSourcePermissions(
-                MutableIndexedMap<String, IndexedListSet<String>>().apply {
-                    put(PERMISSION_NAME_0, MutableIndexedListSet<String>().apply {
-                        add(PERMISSION_NAME_1)
-                    })
-                }
-            )
-            addPermission(mockParsedPermission(PERMISSION_NAME_1, PACKAGE_NAME_0))
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.IMPLICIT_GRANTED or PermissionFlags.IMPLICIT
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime implicit" +
-                " permission that's source from a non-runtime permission, the actual permission" +
-                " flags $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    /**
-     * For a legacy granted or implicit permission during the app upgrade, when the permission
-     * should no longer be legacy or implicit granted, we want to remove the APP_OP_REVOKED flag
-     * so that the app can request the permission.
-     */
-    @Test
-    fun testOnPackageAdded_noLongerLegacyOrImplicitGranted_canBeRequested() {
-        val oldFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.APP_OP_REVOKED or
-            PermissionFlags.RUNTIME_GRANTED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " that is no longer LEGACY_GRANTED or IMPLICIT_GRANTED, the actual permission" +
-                " flags $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_noLongerImplicitPermissions_getsRuntimeAndImplicitFlagsRemoved() {
-        val oldFlags = PermissionFlags.IMPLICIT or PermissionFlags.RUNTIME_GRANTED or
-            PermissionFlags.USER_SET or PermissionFlags.USER_FIXED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " that is no longer implicit and we shouldn't retain as nearby device" +
-                " permissions, the actual permission flags $actualFlags should match the expected" +
-                " flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_noLongerImplicitNearbyPermissionsWasGranted_getsRuntimeGranted() {
-        val oldFlags = PermissionFlags.IMPLICIT_GRANTED or PermissionFlags.IMPLICIT
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_DANGEROUS,
-            permissionName = PERMISSION_BLUETOOTH_CONNECT,
-            requestedPermissions = setOf(
-                PERMISSION_BLUETOOTH_CONNECT,
-                PERMISSION_ACCESS_BACKGROUND_LOCATION
-            )
-        ) {
-            setPermissionFlags(
-                APP_ID_1,
-                USER_ID_0,
-                PERMISSION_ACCESS_BACKGROUND_LOCATION,
-                PermissionFlags.RUNTIME_GRANTED
-            )
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_BLUETOOTH_CONNECT)
-        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime nearby device" +
-                " permission that was granted by implicit, the actual permission flags" +
-                " $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_noLongerImplicitSystemOrPolicyFixedWasGranted_getsRuntimeGranted() {
-        val oldFlags = PermissionFlags.IMPLICIT_GRANTED or PermissionFlags.IMPLICIT or
-            PermissionFlags.SYSTEM_FIXED
-        testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.SYSTEM_FIXED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime permission" +
-                " that was granted and is no longer implicit and is SYSTEM_FIXED or POLICY_FIXED," +
-                " the actual permission flags $actualFlags should match the expected" +
-                " flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_restrictedPermissionsNotExempt_getsRestrictionFlags() {
-        val oldFlags = PermissionFlags.RESTRICTION_REVOKED
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_DANGEROUS,
-            permissionInfoFlags = PermissionInfo.FLAG_HARD_RESTRICTED
-        ) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldFlags
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime hard" +
-                " restricted permission that is not exempted, the actual permission flags" +
-                " $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_restrictedPermissionsIsExempted_clearsRestrictionFlags() {
-        val oldFlags = 0
-        testEvaluatePermissionState(
-            oldFlags,
-            PermissionInfo.PROTECTION_DANGEROUS,
-            permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED
-        ) {}
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.UPGRADE_EXEMPT
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a runtime soft" +
-                " restricted permission that is exempted, the actual permission flags" +
-                " $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_runtimeExistingImplicitPermissions_sourceFlagsNotInherited() {
-        val oldImplicitPermissionFlags = PermissionFlags.USER_FIXED
-        testInheritImplicitPermissionStates(
-            implicitPermissionFlags = oldImplicitPermissionFlags,
-            isNewInstallAndNewPermission = false
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = oldImplicitPermissionFlags or PermissionFlags.IMPLICIT_GRANTED or
-            PermissionFlags.APP_OP_REVOKED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a permission that is" +
-                " implicit, existing and runtime, it should not inherit the runtime flags from" +
-                " the source permission. Hence the actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_nonRuntimeNewImplicitPermissions_sourceFlagsNotInherited() {
-        testInheritImplicitPermissionStates(
-            implicitPermissionProtectionLevel = PermissionInfo.PROTECTION_NORMAL
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a permission that is" +
-                " implicit, new and non-runtime, it should not inherit the runtime flags from" +
-                " the source permission. Hence the actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_runtimeNewImplicitPermissions_sourceFlagsInherited() {
-        val sourceRuntimeFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET
-        testInheritImplicitPermissionStates(sourceRuntimeFlags = sourceRuntimeFlags)
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = sourceRuntimeFlags or PermissionFlags.IMPLICIT_GRANTED or
-            PermissionFlags.IMPLICIT
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a permission that is" +
-                " implicit, new and runtime, it should inherit the runtime flags from" +
-                " the source permission. Hence the actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageAdded_grantingNewFromRevokeImplicitPermissions_onlySourceFlagsInherited() {
-        val sourceRuntimeFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET
-        testInheritImplicitPermissionStates(
-            implicitPermissionFlags = PermissionFlags.POLICY_FIXED,
-            sourceRuntimeFlags = sourceRuntimeFlags,
-            isAnySourcePermissionNonRuntime = false
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = sourceRuntimeFlags or PermissionFlags.IMPLICIT
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a permission that is" +
-                " implicit, existing, runtime and revoked, it should only inherit runtime flags" +
-                " from source permission. Hence the actual permission flags $actualFlags should" +
-                " match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    /**
-     * If it's a media implicit permission (one of RETAIN_IMPLICIT_FLAGS_PERMISSIONS), we want to
-     * remove the IMPLICIT flag so that they will be granted when they are no longer implicit.
-     * (instead of revoking it)
-     */
-    @Test
-    fun testOnPackageAdded_mediaImplicitPermissions_getsImplicitFlagRemoved() {
-        val sourceRuntimeFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET
-        testInheritImplicitPermissionStates(
-            implicitPermissionName = PERMISSION_ACCESS_MEDIA_LOCATION,
-            sourceRuntimeFlags = sourceRuntimeFlags
-        )
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_ACCESS_MEDIA_LOCATION)
-        val expectedNewFlags = sourceRuntimeFlags or PermissionFlags.IMPLICIT_GRANTED
-        assertWithMessage(
-            "After onPackageAdded() is called for a package that requests a media permission that" +
-                " is implicit, new and runtime, it should inherit the runtime flags from" +
-                " the source permission and have the IMPLICIT flag removed. Hence the actual" +
-                " permission flags $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    private fun testInheritImplicitPermissionStates(
-        implicitPermissionName: String = PERMISSION_NAME_0,
-        implicitPermissionFlags: Int = 0,
-        implicitPermissionProtectionLevel: Int = PermissionInfo.PROTECTION_DANGEROUS,
-        sourceRuntimeFlags: Int = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET,
-        isAnySourcePermissionNonRuntime: Boolean = true,
-        isNewInstallAndNewPermission: Boolean = true
-    ) {
-        val implicitPermission = mockParsedPermission(
-            implicitPermissionName,
-            PACKAGE_NAME_0,
-            protectionLevel = implicitPermissionProtectionLevel,
-        )
-        // For source from non-runtime in order to grant by implicit
-        val sourcePermission1 = mockParsedPermission(
-            PERMISSION_NAME_1,
-            PACKAGE_NAME_0,
-            protectionLevel = if (isAnySourcePermissionNonRuntime) {
-                PermissionInfo.PROTECTION_NORMAL
-            } else {
-                PermissionInfo.PROTECTION_DANGEROUS
-            }
-        )
-        // For inheriting runtime flags
-        val sourcePermission2 = mockParsedPermission(
-            PERMISSION_NAME_2,
-            PACKAGE_NAME_0,
-            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS,
-        )
-        val permissionOwnerPackageState = mockPackageState(
-            APP_ID_0,
-            mockAndroidPackage(
-                PACKAGE_NAME_0,
-                permissions = listOf(implicitPermission, sourcePermission1, sourcePermission2)
-            )
-        )
-        val installedPackageState = mockPackageState(
-            APP_ID_1,
-            mockAndroidPackage(
-                PACKAGE_NAME_1,
-                requestedPermissions = setOf(
-                    implicitPermissionName,
-                    PERMISSION_NAME_1,
-                    PERMISSION_NAME_2
-                ),
-                implicitPermissions = setOf(implicitPermissionName)
-            )
-        )
-        oldState.mutateExternalState().setImplicitToSourcePermissions(
-            MutableIndexedMap<String, IndexedListSet<String>>().apply {
-                put(implicitPermissionName, MutableIndexedListSet<String>().apply {
-                    add(PERMISSION_NAME_1)
-                    add(PERMISSION_NAME_2)
-                })
-            }
-        )
-        addPackageState(permissionOwnerPackageState)
-        addPermission(implicitPermission)
-        addPermission(sourcePermission1)
-        addPermission(sourcePermission2)
-        if (!isNewInstallAndNewPermission) {
-            addPackageState(installedPackageState)
-            setPermissionFlags(APP_ID_1, USER_ID_0, implicitPermissionName, implicitPermissionFlags)
-        }
-        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_2, sourceRuntimeFlags)
-
-        mutateState {
-            if (isNewInstallAndNewPermission) {
-                addPackageState(installedPackageState)
-                setPermissionFlags(
-                    APP_ID_1,
-                    USER_ID_0,
-                    implicitPermissionName,
-                    implicitPermissionFlags,
-                    newState
-                )
-            }
-            with(appIdPermissionPolicy) {
-                onPackageAdded(installedPackageState)
-            }
-        }
-    }
-
-    /**
-     * Setup simple package states for testing evaluatePermissionState().
-     * permissionOwnerPackageState is definer of permissionName with APP_ID_0.
-     * installedPackageState is the installed package that requests permissionName with APP_ID_1.
-     *
-     * @param oldFlags the existing permission flags for APP_ID_1, USER_ID_0, permissionName
-     * @param protectionLevel the protectionLevel for the permission
-     * @param permissionName the name of the permission (1) being defined (2) of the oldFlags, and
-     *                       (3) requested by installedPackageState
-     * @param requestedPermissions the permissions requested by installedPackageState
-     * @param implicitPermissions the implicit permissions of installedPackageState
-     * @param permissionInfoFlags the flags for the permission itself
-     * @param isInstalledPackageSystem whether installedPackageState is a system package
-     *
-     * @return installedPackageState
-     */
-    fun testEvaluatePermissionState(
-        oldFlags: Int,
-        protectionLevel: Int,
-        permissionName: String = PERMISSION_NAME_0,
-        requestedPermissions: Set<String> = setOf(permissionName),
-        implicitPermissions: Set<String> = emptySet(),
-        permissionInfoFlags: Int = 0,
-        isInstalledPackageSystem: Boolean = false,
-        isInstalledPackagePrivileged: Boolean = false,
-        isInstalledPackageProduct: Boolean = false,
-        isInstalledPackageSignatureMatching: Boolean = false,
-        isInstalledPackageVendor: Boolean = false,
-        installedPackageTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
-        isNewInstall: Boolean = false,
-        additionalSetup: () -> Unit
-    ) {
-        val parsedPermission = mockParsedPermission(
-            permissionName,
-            PACKAGE_NAME_0,
-            protectionLevel = protectionLevel,
-            flags = permissionInfoFlags
-        )
-        val permissionOwnerPackageState = mockPackageState(
-            APP_ID_0,
-            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
-        )
-        val installedPackageState = mockPackageState(
-            APP_ID_1,
-            mockAndroidPackage(
-                PACKAGE_NAME_1,
-                requestedPermissions = requestedPermissions,
-                implicitPermissions = implicitPermissions,
-                targetSdkVersion = installedPackageTargetSdkVersion,
-                isSignatureMatching = isInstalledPackageSignatureMatching
-            ),
-            isSystem = isInstalledPackageSystem,
-            isPrivileged = isInstalledPackagePrivileged,
-            isProduct = isInstalledPackageProduct,
-            isVendor = isInstalledPackageVendor
-        )
-        addPackageState(permissionOwnerPackageState)
-        if (!isNewInstall) {
-            addPackageState(installedPackageState)
-            setPermissionFlags(APP_ID_1, USER_ID_0, permissionName, oldFlags)
-        }
-        addPermission(parsedPermission)
-
-        additionalSetup()
-
-        mutateState {
-            if (isNewInstall) {
-                addPackageState(installedPackageState, newState)
-                setPermissionFlags(APP_ID_1, USER_ID_0, permissionName, oldFlags, newState)
-            }
-            with(appIdPermissionPolicy) {
-                onPackageAdded(installedPackageState)
-            }
-        }
-    }
-
-    /**
-     * Mock an AndroidPackage with PACKAGE_NAME_0, PERMISSION_NAME_0 and PERMISSION_GROUP_NAME_0
-     */
-    private fun mockSimpleAndroidPackage(): AndroidPackage =
-        mockAndroidPackage(
-            PACKAGE_NAME_0,
-            permissionGroups = listOf(defaultPermissionGroup),
-            permissions = listOf(defaultPermissionTree, defaultPermission)
-        )
-
-    private inline fun mutateState(action: MutateStateScope.() -> Unit) {
-        newState = oldState.toMutable()
-        MutateStateScope(oldState, newState).action()
-    }
-
-    private fun mockPackageState(
-        appId: Int,
-        packageName: String,
-        isSystem: Boolean = false,
-    ): PackageState =
-        mock {
-            whenever(this.appId).thenReturn(appId)
-            whenever(this.packageName).thenReturn(packageName)
-            whenever(androidPackage).thenReturn(null)
-            whenever(this.isSystem).thenReturn(isSystem)
-        }
-
-    private fun mockPackageState(
-        appId: Int,
-        androidPackage: AndroidPackage,
-        isSystem: Boolean = false,
-        isPrivileged: Boolean = false,
-        isProduct: Boolean = false,
-        isInstantApp: Boolean = false,
-        isVendor: Boolean = false
-    ): PackageState =
-        mock {
-            whenever(this.appId).thenReturn(appId)
-            whenever(this.androidPackage).thenReturn(androidPackage)
-            val packageName = androidPackage.packageName
-            whenever(this.packageName).thenReturn(packageName)
-            whenever(this.isSystem).thenReturn(isSystem)
-            whenever(this.isPrivileged).thenReturn(isPrivileged)
-            whenever(this.isProduct).thenReturn(isProduct)
-            whenever(this.isVendor).thenReturn(isVendor)
-            val userStates = SparseArray<PackageUserState>().apply {
-                put(USER_ID_0, mock { whenever(this.isInstantApp).thenReturn(isInstantApp) })
-            }
-            whenever(this.userStates).thenReturn(userStates)
-        }
-
-    private fun mockAndroidPackage(
-        packageName: String,
-        targetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
-        isRequestLegacyExternalStorage: Boolean = false,
-        adoptPermissions: List<String> = emptyList(),
-        implicitPermissions: Set<String> = emptySet(),
-        requestedPermissions: Set<String> = emptySet(),
-        permissionGroups: List<ParsedPermissionGroup> = emptyList(),
-        permissions: List<ParsedPermission> = emptyList(),
-        isSignatureMatching: Boolean = false
-    ): AndroidPackage =
-        mock {
-            whenever(this.packageName).thenReturn(packageName)
-            whenever(this.targetSdkVersion).thenReturn(targetSdkVersion)
-            whenever(this.isRequestLegacyExternalStorage).thenReturn(isRequestLegacyExternalStorage)
-            whenever(this.adoptPermissions).thenReturn(adoptPermissions)
-            whenever(this.implicitPermissions).thenReturn(implicitPermissions)
-            whenever(this.requestedPermissions).thenReturn(requestedPermissions)
-            whenever(this.permissionGroups).thenReturn(permissionGroups)
-            whenever(this.permissions).thenReturn(permissions)
-            val signingDetails = mock<SigningDetails> {
-                whenever(
-                    hasCommonSignerWithCapability(any(), any())
-                ).thenReturn(isSignatureMatching)
-                whenever(hasAncestorOrSelf(any())).thenReturn(isSignatureMatching)
-                whenever(
-                    checkCapability(any<SigningDetails>(), any())
-                ).thenReturn(isSignatureMatching)
-            }
-            whenever(this.signingDetails).thenReturn(signingDetails)
-        }
-
-    private fun mockParsedPermission(
-        permissionName: String,
-        packageName: String,
-        backgroundPermission: String? = null,
-        group: String? = null,
-        protectionLevel: Int = PermissionInfo.PROTECTION_NORMAL,
-        flags: Int = 0,
-        isTree: Boolean = false
-    ): ParsedPermission =
-        mock {
-            whenever(name).thenReturn(permissionName)
-            whenever(this.packageName).thenReturn(packageName)
-            whenever(metaData).thenReturn(Bundle())
-            whenever(this.backgroundPermission).thenReturn(backgroundPermission)
-            whenever(this.group).thenReturn(group)
-            whenever(this.protectionLevel).thenReturn(protectionLevel)
-            whenever(this.flags).thenReturn(flags)
-            whenever(this.isTree).thenReturn(isTree)
-        }
-
-    private fun mockParsedPermissionGroup(
-        permissionGroupName: String,
-        packageName: String,
-    ): ParsedPermissionGroup =
-        mock {
-            whenever(name).thenReturn(permissionGroupName)
-            whenever(this.packageName).thenReturn(packageName)
-            whenever(metaData).thenReturn(Bundle())
-        }
-
-    private fun addPackageState(packageState: PackageState, state: MutableAccessState = oldState) {
-        state.mutateExternalState().apply {
-            setPackageStates(
-                packageStates.toMutableMap().apply {
-                    put(packageState.packageName, packageState)
-                }
-            )
-            mutateAppIdPackageNames().mutateOrPut(packageState.appId) { MutableIndexedListSet() }
-                .add(packageState.packageName)
-        }
-    }
-
-    private fun addDisabledSystemPackageState(
-        packageState: PackageState,
-        state: MutableAccessState = oldState
-    ) = state.mutateExternalState().apply {
-        (disabledSystemPackageStates as ArrayMap)[packageState.packageName] = packageState
-    }
-
-    private fun addPermission(
-        parsedPermission: ParsedPermission,
-        type: Int = Permission.TYPE_MANIFEST,
-        isReconciled: Boolean = true,
-        state: MutableAccessState = oldState
-    ) {
-        val permissionInfo = PackageInfoUtils.generatePermissionInfo(
-            parsedPermission,
-            PackageManager.GET_META_DATA.toLong()
-        )!!
-        val appId = state.externalState.packageStates[permissionInfo.packageName]!!.appId
-        val permission = Permission(permissionInfo, isReconciled, type, appId)
-        if (parsedPermission.isTree) {
-            state.mutateSystemState().mutatePermissionTrees()[permission.name] = permission
-        } else {
-            state.mutateSystemState().mutatePermissions()[permission.name] = permission
-        }
-    }
-
-    private fun addPermissionGroup(
-        parsedPermissionGroup: ParsedPermissionGroup,
-        state: MutableAccessState = oldState
-    ) {
-        state.mutateSystemState().mutatePermissionGroups()[parsedPermissionGroup.name] =
-            PackageInfoUtils.generatePermissionGroupInfo(
-                parsedPermissionGroup,
-                PackageManager.GET_META_DATA.toLong()
-            )!!
-    }
-
-    private fun getPermission(
-        permissionName: String,
-        state: MutableAccessState = newState
-    ): Permission? = state.systemState.permissions[permissionName]
-
-    private fun getPermissionTree(
-        permissionTreeName: String,
-        state: MutableAccessState = newState
-    ): Permission? = state.systemState.permissionTrees[permissionTreeName]
-
-    private fun getPermissionGroup(
-        permissionGroupName: String,
-        state: MutableAccessState = newState
-    ): PermissionGroupInfo? = state.systemState.permissionGroups[permissionGroupName]
-
-    private fun getPermissionFlags(
-        appId: Int,
-        userId: Int,
-        permissionName: String,
-        state: MutableAccessState = newState
-    ): Int =
-        state.userStates[userId]?.appIdPermissionFlags?.get(appId).getWithDefault(permissionName, 0)
-
-    private fun setPermissionFlags(
-        appId: Int,
-        userId: Int,
-        permissionName: String,
-        flags: Int,
-        state: MutableAccessState = oldState
-    ) =
-        state.mutateUserState(userId)!!.mutateAppIdPermissionFlags().mutateOrPut(appId) {
-            MutableIndexedMap()
-        }.put(permissionName, flags)
-
-    companion object {
-        private const val PACKAGE_NAME_0 = "packageName0"
-        private const val PACKAGE_NAME_1 = "packageName1"
-        private const val MISSING_ANDROID_PACKAGE = "missingAndroidPackage"
-        private const val PLATFORM_PACKAGE_NAME = "android"
-
-        private const val APP_ID_0 = 0
-        private const val APP_ID_1 = 1
-        private const val PLATFORM_APP_ID = 2
-
-        private const val PERMISSION_GROUP_NAME_0 = "permissionGroupName0"
-        private const val PERMISSION_GROUP_NAME_1 = "permissionGroupName1"
-
-        private const val PERMISSION_TREE_NAME = "permissionTree"
-
-        private const val PERMISSION_NAME_0 = "permissionName0"
-        private const val PERMISSION_NAME_1 = "permissionName1"
-        private const val PERMISSION_NAME_2 = "permissionName2"
-        private const val PERMISSION_READ_EXTERNAL_STORAGE =
-            Manifest.permission.READ_EXTERNAL_STORAGE
-        private const val PERMISSION_POST_NOTIFICATIONS =
-            Manifest.permission.POST_NOTIFICATIONS
-        private const val PERMISSION_BLUETOOTH_CONNECT =
-            Manifest.permission.BLUETOOTH_CONNECT
-        private const val PERMISSION_ACCESS_BACKGROUND_LOCATION =
-            Manifest.permission.ACCESS_BACKGROUND_LOCATION
-        private const val PERMISSION_ACCESS_MEDIA_LOCATION =
-            Manifest.permission.ACCESS_MEDIA_LOCATION
-
-        private const val USER_ID_0 = 0
-    }
-}
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7966c5c4d961b812153c7c6a23bb235da5e9c89b
--- /dev/null
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.test
+
+import android.Manifest
+import android.content.pm.PackageManager
+import android.content.pm.PermissionGroupInfo
+import android.content.pm.PermissionInfo
+import android.content.pm.SigningDetails
+import android.os.Build
+import android.os.Bundle
+import android.util.ArrayMap
+import android.util.SparseArray
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.server.extendedtestutils.wheneverStatic
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutableUserState
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.permission.AppIdPermissionPolicy
+import com.android.server.permission.access.permission.Permission
+import com.android.server.permission.access.permission.PermissionFlags
+import com.android.server.permission.access.util.hasBits
+import com.android.server.pm.parsing.PackageInfoUtils
+import com.android.server.pm.pkg.AndroidPackage
+import com.android.server.pm.pkg.PackageState
+import com.android.server.pm.pkg.PackageUserState
+import com.android.server.pm.pkg.component.ParsedPermission
+import com.android.server.pm.pkg.component.ParsedPermissionGroup
+import com.android.server.testutils.any
+import com.android.server.testutils.mock
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyLong
+
+/**
+ * Mocking unit test for AppIdPermissionPolicy.
+ */
+@RunWith(AndroidJUnit4::class)
+open class BaseAppIdPermissionPolicyTest {
+    protected lateinit var oldState: MutableAccessState
+    protected lateinit var newState: MutableAccessState
+
+    protected val defaultPermissionGroup = mockParsedPermissionGroup(
+        PERMISSION_GROUP_NAME_0,
+        PACKAGE_NAME_0
+    )
+    protected val defaultPermissionTree = mockParsedPermission(
+        PERMISSION_TREE_NAME,
+        PACKAGE_NAME_0,
+        isTree = true
+    )
+    protected val defaultPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0)
+
+    protected val appIdPermissionPolicy = AppIdPermissionPolicy()
+
+    @Rule
+    @JvmField
+    val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
+        .spyStatic(PackageInfoUtils::class.java)
+        .build()
+
+    @Before
+    open fun setUp() {
+        oldState = MutableAccessState()
+        createUserState(USER_ID_0)
+        oldState.mutateExternalState().setPackageStates(ArrayMap())
+        oldState.mutateExternalState().setDisabledSystemPackageStates(ArrayMap())
+        mockPackageInfoUtilsGeneratePermissionInfo()
+        mockPackageInfoUtilsGeneratePermissionGroupInfo()
+    }
+
+    protected fun createUserState(userId: Int) {
+        oldState.mutateExternalState().mutateUserIds().add(userId)
+        oldState.mutateUserStatesNoWrite().put(userId, MutableUserState())
+    }
+
+    private fun mockPackageInfoUtilsGeneratePermissionInfo() {
+        wheneverStatic {
+            PackageInfoUtils.generatePermissionInfo(any(ParsedPermission::class.java), anyLong())
+        }.thenAnswer { invocation ->
+            val parsedPermission = invocation.getArgument<ParsedPermission>(0)
+            val generateFlags = invocation.getArgument<Long>(1)
+            PermissionInfo(parsedPermission.backgroundPermission).apply {
+                name = parsedPermission.name
+                packageName = parsedPermission.packageName
+                metaData = if (generateFlags.toInt().hasBits(PackageManager.GET_META_DATA)) {
+                    parsedPermission.metaData
+                } else {
+                    null
+                }
+                @Suppress("DEPRECATION")
+                protectionLevel = parsedPermission.protectionLevel
+                group = parsedPermission.group
+                flags = parsedPermission.flags
+            }
+        }
+    }
+
+    private fun mockPackageInfoUtilsGeneratePermissionGroupInfo() {
+        wheneverStatic {
+            PackageInfoUtils.generatePermissionGroupInfo(
+                any(ParsedPermissionGroup::class.java),
+                anyLong()
+            )
+        }.thenAnswer { invocation ->
+            val parsedPermissionGroup = invocation.getArgument<ParsedPermissionGroup>(0)
+            val generateFlags = invocation.getArgument<Long>(1)
+            @Suppress("DEPRECATION")
+            PermissionGroupInfo().apply {
+                name = parsedPermissionGroup.name
+                packageName = parsedPermissionGroup.packageName
+                metaData = if (generateFlags.toInt().hasBits(PackageManager.GET_META_DATA)) {
+                    parsedPermissionGroup.metaData
+                } else {
+                    null
+                }
+                flags = parsedPermissionGroup.flags
+            }
+        }
+    }
+
+    @Test
+    fun testOnAppIdRemoved_appIdIsRemoved_permissionFlagsCleared() {
+        val parsedPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0)
+        val permissionOwnerPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
+        )
+        val requestingPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+        )
+        addPackageState(permissionOwnerPackageState)
+        addPackageState(requestingPackageState)
+        addPermission(parsedPermission)
+        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.INSTALL_GRANTED)
+
+        mutateState {
+            with(appIdPermissionPolicy) {
+                onAppIdRemoved(APP_ID_1)
+            }
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After onAppIdRemoved() is called for appId $APP_ID_1 that requests a permission" +
+                " owns by appId $APP_ID_0 with existing permission flags. The actual permission" +
+                " flags $actualFlags should be null"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testOnPackageRemoved_packageIsRemoved_permissionsAreTrimmedAndStatesAreEvaluated() {
+        // TODO
+        // shouldn't reuse test cases because it's really different despite it's also for
+        // trim permission states. It's different because it's package removal
+    }
+
+    @Test
+    fun testOnPackageInstalled_nonSystemAppIsInstalled_upgradeExemptFlagIsCleared() {
+        // TODO
+        // should be fine for it to be its own test cases and not to re-use
+        // clearRestrictedPermissionImplicitExemption
+    }
+
+    @Test
+    fun testOnPackageInstalled_systemAppIsInstalled_upgradeExemptFlagIsRetained() {
+        // TODO
+    }
+
+    @Test
+    fun testOnPackageInstalled_requestedPermissionAlsoRequestedBySystemApp_exemptFlagIsRetained() {
+        // TODO
+    }
+
+    @Test
+    fun testOnPackageInstalled_restrictedPermissionsNotExempt_getsRestrictionFlags() {
+        // TODO
+    }
+
+    @Test
+    fun testOnPackageInstalled_restrictedPermissionsIsExempted_clearsRestrictionFlags() {
+        // TODO
+    }
+
+    @Test
+    fun testOnStateMutated_notEmpty_isCalledForEachListener() {
+        // TODO
+    }
+
+    /**
+     * Mock an AndroidPackage with PACKAGE_NAME_0, PERMISSION_NAME_0 and PERMISSION_GROUP_NAME_0
+     */
+    protected fun mockSimpleAndroidPackage(): AndroidPackage =
+        mockAndroidPackage(
+            PACKAGE_NAME_0,
+            permissionGroups = listOf(defaultPermissionGroup),
+            permissions = listOf(defaultPermissionTree, defaultPermission)
+        )
+
+    protected inline fun mutateState(action: MutateStateScope.() -> Unit) {
+        newState = oldState.toMutable()
+        MutateStateScope(oldState, newState).action()
+    }
+
+    protected fun mockPackageState(
+        appId: Int,
+        packageName: String,
+        isSystem: Boolean = false,
+    ): PackageState =
+        mock {
+            whenever(this.appId).thenReturn(appId)
+            whenever(this.packageName).thenReturn(packageName)
+            whenever(androidPackage).thenReturn(null)
+            whenever(this.isSystem).thenReturn(isSystem)
+        }
+
+    protected fun mockPackageState(
+        appId: Int,
+        androidPackage: AndroidPackage,
+        isSystem: Boolean = false,
+        isPrivileged: Boolean = false,
+        isProduct: Boolean = false,
+        isInstantApp: Boolean = false,
+        isVendor: Boolean = false
+    ): PackageState =
+        mock {
+            whenever(this.appId).thenReturn(appId)
+            whenever(this.androidPackage).thenReturn(androidPackage)
+            val packageName = androidPackage.packageName
+            whenever(this.packageName).thenReturn(packageName)
+            whenever(this.isSystem).thenReturn(isSystem)
+            whenever(this.isPrivileged).thenReturn(isPrivileged)
+            whenever(this.isProduct).thenReturn(isProduct)
+            whenever(this.isVendor).thenReturn(isVendor)
+            val userStates = SparseArray<PackageUserState>().apply {
+                put(USER_ID_0, mock { whenever(this.isInstantApp).thenReturn(isInstantApp) })
+            }
+            whenever(this.userStates).thenReturn(userStates)
+        }
+
+    protected fun mockAndroidPackage(
+        packageName: String,
+        targetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+        isRequestLegacyExternalStorage: Boolean = false,
+        adoptPermissions: List<String> = emptyList(),
+        implicitPermissions: Set<String> = emptySet(),
+        requestedPermissions: Set<String> = emptySet(),
+        permissionGroups: List<ParsedPermissionGroup> = emptyList(),
+        permissions: List<ParsedPermission> = emptyList(),
+        isSignatureMatching: Boolean = false
+    ): AndroidPackage =
+        mock {
+            whenever(this.packageName).thenReturn(packageName)
+            whenever(this.targetSdkVersion).thenReturn(targetSdkVersion)
+            whenever(this.isRequestLegacyExternalStorage).thenReturn(isRequestLegacyExternalStorage)
+            whenever(this.adoptPermissions).thenReturn(adoptPermissions)
+            whenever(this.implicitPermissions).thenReturn(implicitPermissions)
+            whenever(this.requestedPermissions).thenReturn(requestedPermissions)
+            whenever(this.permissionGroups).thenReturn(permissionGroups)
+            whenever(this.permissions).thenReturn(permissions)
+            val signingDetails = mock<SigningDetails> {
+                whenever(
+                    hasCommonSignerWithCapability(any(), any())
+                ).thenReturn(isSignatureMatching)
+                whenever(hasAncestorOrSelf(any())).thenReturn(isSignatureMatching)
+                whenever(
+                    checkCapability(any<SigningDetails>(), any())
+                ).thenReturn(isSignatureMatching)
+            }
+            whenever(this.signingDetails).thenReturn(signingDetails)
+        }
+
+    protected fun mockParsedPermission(
+        permissionName: String,
+        packageName: String,
+        backgroundPermission: String? = null,
+        group: String? = null,
+        protectionLevel: Int = PermissionInfo.PROTECTION_NORMAL,
+        flags: Int = 0,
+        isTree: Boolean = false
+    ): ParsedPermission =
+        mock {
+            whenever(name).thenReturn(permissionName)
+            whenever(this.packageName).thenReturn(packageName)
+            whenever(metaData).thenReturn(Bundle())
+            whenever(this.backgroundPermission).thenReturn(backgroundPermission)
+            whenever(this.group).thenReturn(group)
+            whenever(this.protectionLevel).thenReturn(protectionLevel)
+            whenever(this.flags).thenReturn(flags)
+            whenever(this.isTree).thenReturn(isTree)
+        }
+
+    protected fun mockParsedPermissionGroup(
+        permissionGroupName: String,
+        packageName: String,
+    ): ParsedPermissionGroup =
+        mock {
+            whenever(name).thenReturn(permissionGroupName)
+            whenever(this.packageName).thenReturn(packageName)
+            whenever(metaData).thenReturn(Bundle())
+        }
+
+    protected fun addPackageState(
+        packageState: PackageState,
+        state: MutableAccessState = oldState
+    ) {
+        state.mutateExternalState().apply {
+            setPackageStates(
+                packageStates.toMutableMap().apply {
+                    put(packageState.packageName, packageState)
+                }
+            )
+            mutateAppIdPackageNames().mutateOrPut(packageState.appId) { MutableIndexedListSet() }
+                .add(packageState.packageName)
+        }
+    }
+
+    protected fun addDisabledSystemPackageState(
+        packageState: PackageState,
+        state: MutableAccessState = oldState
+    ) = state.mutateExternalState().apply {
+        (disabledSystemPackageStates as ArrayMap)[packageState.packageName] = packageState
+    }
+
+    protected fun addPermission(
+        parsedPermission: ParsedPermission,
+        type: Int = Permission.TYPE_MANIFEST,
+        isReconciled: Boolean = true,
+        state: MutableAccessState = oldState
+    ) {
+        val permissionInfo = PackageInfoUtils.generatePermissionInfo(
+            parsedPermission,
+            PackageManager.GET_META_DATA.toLong()
+        )!!
+        val appId = state.externalState.packageStates[permissionInfo.packageName]!!.appId
+        val permission = Permission(permissionInfo, isReconciled, type, appId)
+        if (parsedPermission.isTree) {
+            state.mutateSystemState().mutatePermissionTrees()[permission.name] = permission
+        } else {
+            state.mutateSystemState().mutatePermissions()[permission.name] = permission
+        }
+    }
+
+    protected fun addPermissionGroup(
+        parsedPermissionGroup: ParsedPermissionGroup,
+        state: MutableAccessState = oldState
+    ) {
+        state.mutateSystemState().mutatePermissionGroups()[parsedPermissionGroup.name] =
+            PackageInfoUtils.generatePermissionGroupInfo(
+                parsedPermissionGroup,
+                PackageManager.GET_META_DATA.toLong()
+            )!!
+    }
+
+    protected fun getPermission(
+        permissionName: String,
+        state: MutableAccessState = newState
+    ): Permission? = state.systemState.permissions[permissionName]
+
+    protected fun getPermissionTree(
+        permissionTreeName: String,
+        state: MutableAccessState = newState
+    ): Permission? = state.systemState.permissionTrees[permissionTreeName]
+
+    protected fun getPermissionGroup(
+        permissionGroupName: String,
+        state: MutableAccessState = newState
+    ): PermissionGroupInfo? = state.systemState.permissionGroups[permissionGroupName]
+
+    protected fun getPermissionFlags(
+        appId: Int,
+        userId: Int,
+        permissionName: String,
+        state: MutableAccessState = newState
+    ): Int =
+        state.userStates[userId]?.appIdPermissionFlags?.get(appId).getWithDefault(permissionName, 0)
+
+    protected fun setPermissionFlags(
+        appId: Int,
+        userId: Int,
+        permissionName: String,
+        flags: Int,
+        state: MutableAccessState = oldState
+    ) =
+        state.mutateUserState(userId)!!.mutateAppIdPermissionFlags().mutateOrPut(appId) {
+            MutableIndexedMap()
+        }.put(permissionName, flags)
+
+    companion object {
+        @JvmStatic protected val PACKAGE_NAME_0 = "packageName0"
+        @JvmStatic protected val PACKAGE_NAME_1 = "packageName1"
+        @JvmStatic protected val PACKAGE_NAME_2 = "packageName2"
+        @JvmStatic protected val MISSING_ANDROID_PACKAGE = "missingAndroidPackage"
+        @JvmStatic protected val PLATFORM_PACKAGE_NAME = "android"
+
+        @JvmStatic protected val APP_ID_0 = 0
+        @JvmStatic protected val APP_ID_1 = 1
+        @JvmStatic protected val PLATFORM_APP_ID = 2
+
+        @JvmStatic protected val PERMISSION_GROUP_NAME_0 = "permissionGroupName0"
+        @JvmStatic protected val PERMISSION_GROUP_NAME_1 = "permissionGroupName1"
+
+        @JvmStatic protected val PERMISSION_TREE_NAME = "permissionTree"
+
+        @JvmStatic protected val PERMISSION_NAME_0 = "permissionName0"
+        @JvmStatic protected val PERMISSION_NAME_1 = "permissionName1"
+        @JvmStatic protected val PERMISSION_NAME_2 = "permissionName2"
+        @JvmStatic protected val PERMISSION_READ_EXTERNAL_STORAGE =
+            Manifest.permission.READ_EXTERNAL_STORAGE
+        @JvmStatic protected val PERMISSION_POST_NOTIFICATIONS =
+            Manifest.permission.POST_NOTIFICATIONS
+        @JvmStatic protected val PERMISSION_BLUETOOTH_CONNECT =
+            Manifest.permission.BLUETOOTH_CONNECT
+        @JvmStatic protected val PERMISSION_ACCESS_BACKGROUND_LOCATION =
+            Manifest.permission.ACCESS_BACKGROUND_LOCATION
+        @JvmStatic protected val PERMISSION_ACCESS_MEDIA_LOCATION =
+            Manifest.permission.ACCESS_MEDIA_LOCATION
+
+        @JvmStatic protected val USER_ID_0 = 0
+        @JvmStatic protected val USER_ID_NEW = 1
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index fbad369ab19eab71c21cb5e0d6d8c60b18af74e6..b8c18e070397fae63245ea37e8f4d703d7100a70 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -91,6 +91,7 @@ import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.display.DisplayDeviceConfig;
 import com.android.server.display.TestUtils;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.mode.DisplayModeDirector.BrightnessObserver;
 import com.android.server.display.mode.DisplayModeDirector.DesiredDisplayModeSpecs;
 import com.android.server.sensors.SensorManagerInternal;
@@ -110,7 +111,9 @@ import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -121,10 +124,114 @@ import junitparams.Parameters;
 @SmallTest
 @RunWith(JUnitParamsRunner.class)
 public class DisplayModeDirectorTest {
-    // The tolerance within which we consider something approximately equals.
+    public static Collection<Object[]> getAppRequestedSizeTestCases() {
+        var appRequestedSizeTestCases = Arrays.asList(new Object[][] {
+                {DEFAULT_MODE_75.getModeId(), Float.POSITIVE_INFINITY,
+                        DEFAULT_MODE_75.getRefreshRate(), Map.of()},
+                {APP_MODE_HIGH_90.getModeId(), Float.POSITIVE_INFINITY,
+                        APP_MODE_HIGH_90.getRefreshRate(),
+                        Map.of(
+                                Vote.PRIORITY_APP_REQUEST_SIZE,
+                                Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
+                                        APP_MODE_HIGH_90.getPhysicalHeight()),
+                                Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                                Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()))},
+                {LIMIT_MODE_70.getModeId(), Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
+                        Map.of(
+                                Vote.PRIORITY_APP_REQUEST_SIZE,
+                                Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
+                                        APP_MODE_HIGH_90.getPhysicalHeight()),
+                                Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                                Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()),
+                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),
+                                        LIMIT_MODE_70.getPhysicalHeight()))},
+                {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(),
+                        LIMIT_MODE_70.getRefreshRate(),
+                        Map.of(
+                                Vote.PRIORITY_APP_REQUEST_SIZE,
+                                Vote.forSize(APP_MODE_65.getPhysicalWidth(),
+                                        APP_MODE_65.getPhysicalHeight()),
+                                Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                                Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()),
+                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),
+                                        LIMIT_MODE_70.getPhysicalHeight()))},
+                {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(),
+                        LIMIT_MODE_70.getRefreshRate(),
+                        Map.of(
+                                Vote.PRIORITY_APP_REQUEST_SIZE,
+                                Vote.forSize(APP_MODE_65.getPhysicalWidth(),
+                                        APP_MODE_65.getPhysicalHeight()),
+                                Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                                Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()),
+                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.forSizeAndPhysicalRefreshRatesRange(
+                                    0, 0,
+                                    LIMIT_MODE_70.getPhysicalWidth(),
+                                    LIMIT_MODE_70.getPhysicalHeight(),
+                                    0, Float.POSITIVE_INFINITY)), false},
+                {APP_MODE_65.getModeId(), APP_MODE_65.getRefreshRate(),
+                        APP_MODE_65.getRefreshRate(),
+                        Map.of(
+                                Vote.PRIORITY_APP_REQUEST_SIZE,
+                                Vote.forSize(APP_MODE_65.getPhysicalWidth(),
+                                        APP_MODE_65.getPhysicalHeight()),
+                                Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                                Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()),
+                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.forSizeAndPhysicalRefreshRatesRange(
+                                    0, 0,
+                                    LIMIT_MODE_70.getPhysicalWidth(),
+                                    LIMIT_MODE_70.getPhysicalHeight(),
+                                    0, Float.POSITIVE_INFINITY)), true}});
+
+        final var res = new ArrayList<Object[]>(appRequestedSizeTestCases.size() * 2);
+
+        // Add additional argument for displayResolutionRangeVotingEnabled=false if not present.
+        for (var testCaseArrayArgs : appRequestedSizeTestCases) {
+            if (testCaseArrayArgs.length == 4) {
+                var testCaseListArgs = new ArrayList<>(Arrays.asList(testCaseArrayArgs));
+                testCaseListArgs.add(/* displayResolutionRangeVotingEnabled */ false);
+                res.add(testCaseListArgs.toArray());
+            } else {
+                res.add(testCaseArrayArgs);
+            }
+        }
+
+        // Add additional argument for displayResolutionRangeVotingEnabled=true if not present.
+        for (var testCaseArrayArgs : appRequestedSizeTestCases) {
+            if (testCaseArrayArgs.length == 4) {
+                var testCaseListArgs = new ArrayList<>(Arrays.asList(testCaseArrayArgs));
+                testCaseListArgs.add(/* displayResolutionRangeVotingEnabled */ true);
+                res.add(testCaseListArgs.toArray());
+            }
+        }
+
+        return res;
+    }
+
     private static final String TAG = "DisplayModeDirectorTest";
     private static final boolean DEBUG = false;
     private static final float FLOAT_TOLERANCE = 0.01f;
+
+    private static final Display.Mode APP_MODE_65 = new Display.Mode(
+            /*modeId=*/65, /*width=*/1900, /*height=*/1900, 65);
+    private static final Display.Mode LIMIT_MODE_70 = new Display.Mode(
+            /*modeId=*/70, /*width=*/2000, /*height=*/2000, 70);
+    private static final Display.Mode DEFAULT_MODE_75 = new Display.Mode(
+            /*modeId=*/75, /*width=*/2500, /*height=*/2500, 75);
+    private static final Display.Mode APP_MODE_HIGH_90 = new Display.Mode(
+            /*modeId=*/90, /*width=*/3000, /*height=*/3000, 90);
+    private static final Display.Mode[] TEST_MODES = new Display.Mode[] {
+        new Display.Mode(
+            /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60),
+        APP_MODE_65,
+        LIMIT_MODE_70,
+        DEFAULT_MODE_75,
+        APP_MODE_HIGH_90
+    };
+
     private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
     private static final int MODE_ID = 1;
     private static final float TRANSITION_POINT = 0.763f;
@@ -142,6 +249,8 @@ public class DisplayModeDirectorTest {
     public SensorManagerInternal mSensorManagerInternalMock;
     @Mock
     public DisplayManagerInternal mDisplayManagerInternalMock;
+    @Mock
+    private DisplayManagerFlags mDisplayManagerFlags;
 
     @Before
     public void setUp() throws Exception {
@@ -177,7 +286,7 @@ public class DisplayModeDirectorTest {
     private DisplayModeDirector createDirectorFromModeArray(Display.Mode[] modes,
             Display.Mode defaultMode) {
         DisplayModeDirector director =
-                new DisplayModeDirector(mContext, mHandler, mInjector);
+                new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
         director.setLoggingEnabled(true);
         SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
         supportedModesByDisplay.put(DISPLAY_ID, modes);
@@ -219,9 +328,8 @@ public class DisplayModeDirectorTest {
         // should take precedence over lower priority votes.
         {
             int minFps = 60;
-            int maxFps = 90;
-            director = createDirectorFromFpsRange(60, 90);
-            assertTrue(2 * numPriorities < maxFps - minFps + 1);
+            int maxFps = minFps + 2 * numPriorities;
+            director = createDirectorFromFpsRange(minFps, maxFps);
             SparseArray<Vote> votes = new SparseArray<>();
             SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
             votesByDisplay.put(DISPLAY_ID, votes);
@@ -472,6 +580,7 @@ public class DisplayModeDirectorTest {
         assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(90);
     }
 
+    /** Resolution range voting disabled */
     @Test
     public void testAppRequestRefreshRateRange() {
         // Confirm that the app request range doesn't include flicker or min refresh rate settings,
@@ -530,6 +639,33 @@ public class DisplayModeDirectorTest {
         assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
     }
 
+    /** Tests for app requested size */
+    @Parameters(method = "getAppRequestedSizeTestCases")
+    @Test
+    public void testAppRequestedSize(final int expectedBaseModeId,
+                final float expectedPhysicalRefreshRate,
+                final float expectedAppRequestedRefreshRate,
+                final Map<Integer, Vote> votesWithPriorities,
+                final boolean displayResolutionRangeVotingEnabled) {
+        when(mDisplayManagerFlags.isDisplayResolutionRangeVotingEnabled())
+                .thenReturn(displayResolutionRangeVotingEnabled);
+        DisplayModeDirector director = createDirectorFromModeArray(TEST_MODES, DEFAULT_MODE_75);
+
+        SparseArray<Vote> votes = new SparseArray<>();
+        votesWithPriorities.forEach(votes::put);
+
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        director.injectVotesByDisplay(votesByDisplay);
+
+        var desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(expectedBaseModeId);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.primary.physical.max).isAtLeast(expectedPhysicalRefreshRate);
+        assertThat(desiredSpecs.appRequest.physical.min).isAtMost(0);
+        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(expectedAppRequestedRefreshRate);
+    }
+
     void verifySpecsWithRefreshRateSettings(DisplayModeDirector director, float minFps,
             float peakFps, float defaultFps, RefreshRateRanges primary,
             RefreshRateRanges appRequest) {
@@ -843,7 +979,7 @@ public class DisplayModeDirectorTest {
     @Test
     public void testStaleAppRequestSize() {
         DisplayModeDirector director =
-                new DisplayModeDirector(mContext, mHandler, mInjector);
+                new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
         Display.Mode[] modes = new Display.Mode[] {
                 new Display.Mode(1, 1280, 720, 60),
         };
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff91d34470d434b6ae3edd75e759806a96a255fd
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode;
+
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.Mode.INVALID_MODE_ID;
+
+
+import static com.android.server.display.mode.DisplayModeDirector.SYNCHRONIZED_REFRESH_RATE_TOLERANCE;
+import static com.android.server.display.mode.Vote.PRIORITY_LIMIT_MODE;
+import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE;
+import static com.android.server.display.mode.Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE;
+import static com.android.server.display.mode.VotesStorage.GLOBAL_ID;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.DeviceConfigInterface;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.sensors.SensorManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import junitparams.JUnitParamsRunner;
+
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class DisplayObserverTest {
+    private static final int EXTERNAL_DISPLAY = 1;
+    private static final int MAX_WIDTH = 1920;
+    private static final int MAX_HEIGHT = 1080;
+    private static final int MAX_REFRESH_RATE = 60;
+
+    private final Display.Mode[] mInternalDisplayModes = new Display.Mode[] {
+            new Display.Mode(/*modeId=*/ 0, MAX_WIDTH / 2, MAX_HEIGHT / 2,
+                    (float) MAX_REFRESH_RATE / 2),
+            new Display.Mode(/*modeId=*/ 1, MAX_WIDTH / 2, MAX_HEIGHT / 2,
+                    MAX_REFRESH_RATE),
+            new Display.Mode(/*modeId=*/ 2, MAX_WIDTH / 2, MAX_HEIGHT / 2,
+                    MAX_REFRESH_RATE * 2),
+            new Display.Mode(/*modeId=*/ 3, MAX_WIDTH, MAX_HEIGHT, MAX_REFRESH_RATE * 2),
+            new Display.Mode(/*modeId=*/ 4, MAX_WIDTH, MAX_HEIGHT, MAX_REFRESH_RATE),
+            new Display.Mode(/*modeId=*/ 5, MAX_WIDTH * 2, MAX_HEIGHT * 2,
+                    MAX_REFRESH_RATE),
+            new Display.Mode(/*modeId=*/ 6, MAX_WIDTH / 2, MAX_HEIGHT / 2,
+                    MAX_REFRESH_RATE * 3),
+    };
+
+    private final Display.Mode[] mExternalDisplayModes = new Display.Mode[] {
+            new Display.Mode(/*modeId=*/ 0, MAX_WIDTH / 2, MAX_HEIGHT / 2,
+                    (float) MAX_REFRESH_RATE / 2),
+            new Display.Mode(/*modeId=*/ 1, MAX_WIDTH / 2, MAX_HEIGHT / 2,
+                    MAX_REFRESH_RATE),
+            new Display.Mode(/*modeId=*/ 2, MAX_WIDTH / 2, MAX_HEIGHT / 2,
+                    MAX_REFRESH_RATE * 2),
+            new Display.Mode(/*modeId=*/ 3, MAX_WIDTH, MAX_HEIGHT, MAX_REFRESH_RATE * 2),
+            new Display.Mode(/*modeId=*/ 4, MAX_WIDTH, MAX_HEIGHT, MAX_REFRESH_RATE),
+            new Display.Mode(/*modeId=*/ 5, MAX_WIDTH * 2, MAX_HEIGHT * 2,
+                    MAX_REFRESH_RATE),
+    };
+
+    private DisplayModeDirector mDmd;
+    private Context mContext;
+    private DisplayModeDirector.Injector mInjector;
+    private Handler mHandler;
+    private DisplayManager.DisplayListener mObserver;
+    private Resources mResources;
+    @Mock
+    private DisplayManagerFlags mDisplayManagerFlags;
+    private int mExternalDisplayUserPreferredModeId = INVALID_MODE_ID;
+    private int mInternalDisplayUserPreferredModeId = INVALID_MODE_ID;
+    private Display mDefaultDisplay;
+    private Display mExternalDisplay;
+
+    /** Setup tests. */
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mHandler = new Handler(Looper.getMainLooper());
+        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        mResources = mock(Resources.class);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate))
+                .thenReturn(0);
+        when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth))
+                .thenReturn(0);
+        when(mResources.getInteger(R.integer.config_externalDisplayPeakHeight))
+                .thenReturn(0);
+        when(mResources.getBoolean(R.bool.config_refreshRateSynchronizationEnabled))
+                .thenReturn(false);
+
+        // Necessary configs to initialize DisplayModeDirector
+        when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+                .thenReturn(new int[]{5});
+        when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+                .thenReturn(new int[]{10});
+        when(mResources.getIntArray(
+                R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+                .thenReturn(new int[]{250});
+        when(mResources.getIntArray(
+                R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+                .thenReturn(new int[]{7000});
+    }
+
+    /** No vote for user preferred mode */
+    @Test
+    public void testExternalDisplay_notVotedUserPreferredMode() {
+        var preferredMode = mExternalDisplayModes[5];
+        mExternalDisplayUserPreferredModeId = preferredMode.getModeId();
+
+        init();
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+
+        // Testing that the vote is not added when display is added because feature is disabled
+        mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+
+        // Testing that the vote is not present after display is removed
+        mObserver.onDisplayRemoved(EXTERNAL_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+
+        // Testing that the vote is not added when display is changed because feature is disabled
+        mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+    }
+
+    /** Vote for user preferred mode */
+    @Test
+    public void testExternalDisplay_voteUserPreferredMode() {
+        when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+        var preferredMode = mExternalDisplayModes[5];
+        mExternalDisplayUserPreferredModeId = preferredMode.getModeId();
+        var expectedVote = Vote.forSize(
+                        preferredMode.getPhysicalWidth(),
+                        preferredMode.getPhysicalHeight());
+        init();
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+        mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(expectedVote);
+
+        mExternalDisplayUserPreferredModeId = INVALID_MODE_ID;
+        mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+
+        preferredMode = mExternalDisplayModes[4];
+        mExternalDisplayUserPreferredModeId = preferredMode.getModeId();
+        expectedVote = Vote.forSize(
+                        preferredMode.getPhysicalWidth(),
+                        preferredMode.getPhysicalHeight());
+        mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(expectedVote);
+
+        // Testing that the vote is removed.
+        mObserver.onDisplayRemoved(EXTERNAL_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+    }
+
+    /** External display: Do not apply limit to user preferred mode */
+    @Test
+    public void testExternalDisplay_doNotApplyLimitToUserPreferredMode() {
+        when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true);
+        when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate))
+                .thenReturn(MAX_REFRESH_RATE);
+        when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth))
+                .thenReturn(MAX_WIDTH);
+        when(mResources.getInteger(R.integer.config_externalDisplayPeakHeight))
+                .thenReturn(MAX_HEIGHT);
+
+        var preferredMode = mExternalDisplayModes[5];
+        mExternalDisplayUserPreferredModeId = preferredMode.getModeId();
+        var expectedResolutionVote = Vote.forSize(preferredMode.getPhysicalWidth(),
+                preferredMode.getPhysicalHeight());
+        init();
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+        mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(expectedResolutionVote);
+
+        // Testing that the vote is removed.
+        mObserver.onDisplayRemoved(EXTERNAL_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+    }
+
+    /** Default display: Do not apply limit to user preferred mode */
+    @Test
+    public void testDefaultDisplayAdded_notAppliedLimitToUserPreferredMode() {
+        when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true);
+        when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate))
+                .thenReturn(MAX_REFRESH_RATE);
+        when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth))
+                .thenReturn(MAX_WIDTH);
+        when(mResources.getInteger(R.integer.config_externalDisplayPeakHeight))
+                .thenReturn(MAX_HEIGHT);
+        var preferredMode = mInternalDisplayModes[5];
+        mInternalDisplayUserPreferredModeId = preferredMode.getModeId();
+        var expectedResolutionVote = Vote.forSize(preferredMode.getPhysicalWidth(),
+                        preferredMode.getPhysicalHeight());
+        init();
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+        mObserver.onDisplayAdded(DEFAULT_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(expectedResolutionVote);
+        mObserver.onDisplayRemoved(DEFAULT_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+                .isEqualTo(null);
+    }
+
+    /** Default display added, no mode limit set */
+    @Test
+    public void testDefaultDisplayAdded() {
+        when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true);
+        when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate))
+                .thenReturn(MAX_REFRESH_RATE);
+        when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth))
+                .thenReturn(MAX_WIDTH);
+        when(mResources.getInteger(R.integer.config_externalDisplayPeakHeight))
+                .thenReturn(MAX_HEIGHT);
+        init();
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
+        mObserver.onDisplayAdded(DEFAULT_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
+    }
+
+    /** External display added, apply resolution refresh rate limit */
+    @Test
+    public void testExternalDisplayAdded_applyResolutionRefreshRateLimit() {
+        when(mDisplayManagerFlags.isDisplayResolutionRangeVotingEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true);
+        when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate))
+                .thenReturn(MAX_REFRESH_RATE);
+        when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth))
+                .thenReturn(MAX_WIDTH);
+        when(mResources.getInteger(R.integer.config_externalDisplayPeakHeight))
+                .thenReturn(MAX_HEIGHT);
+        init();
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
+        mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(
+                Vote.forSizeAndPhysicalRefreshRatesRange(0, 0,
+                        MAX_WIDTH, MAX_HEIGHT,
+                        /*minPhysicalRefreshRate=*/ 0, MAX_REFRESH_RATE));
+        mObserver.onDisplayRemoved(EXTERNAL_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
+    }
+
+    /** External display added, disabled resolution refresh rate limit. */
+    @Test
+    public void testExternalDisplayAdded_disabledResolutionRefreshRateLimit() {
+        when(mDisplayManagerFlags.isDisplayResolutionRangeVotingEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true);
+        init();
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
+        mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
+        mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
+        mObserver.onDisplayRemoved(EXTERNAL_DISPLAY);
+        assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
+        assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
+    }
+
+    /** External display added, applied refresh rates synchronization */
+    @Test
+    public void testExternalDisplayAdded_appliedRefreshRatesSynchronization() {
+        when(mDisplayManagerFlags.isDisplayResolutionRangeVotingEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_refreshRateSynchronizationEnabled))
+                .thenReturn(true);
+        init();
+        assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
+        mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
+        assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(
+                Vote.forPhysicalRefreshRates(
+                        MAX_REFRESH_RATE - SYNCHRONIZED_REFRESH_RATE_TOLERANCE,
+                        MAX_REFRESH_RATE + SYNCHRONIZED_REFRESH_RATE_TOLERANCE));
+
+        // Remove external display and check that sync vote is no longer present.
+        mObserver.onDisplayRemoved(EXTERNAL_DISPLAY);
+
+        assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
+    }
+
+    /** External display added, disabled feature refresh rates synchronization */
+    @Test
+    public void testExternalDisplayAdded_disabledFeatureRefreshRatesSynchronization() {
+        when(mDisplayManagerFlags.isDisplayResolutionRangeVotingEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(false);
+        when(mResources.getBoolean(R.bool.config_refreshRateSynchronizationEnabled))
+                .thenReturn(true);
+        init();
+        assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
+        mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
+        assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
+    }
+
+    /** External display not applied refresh rates synchronization, because
+     * config_refreshRateSynchronizationEnabled is false. */
+    @Test
+    public void testExternalDisplay_notAppliedRefreshRatesSynchronization() {
+        when(mDisplayManagerFlags.isDisplayResolutionRangeVotingEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true);
+        when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(true);
+        init();
+        assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
+        mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
+        assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
+    }
+
+    private void init() {
+        mInjector = mock(DisplayModeDirector.Injector.class);
+        doAnswer(invocation -> {
+            assertThat(mObserver).isNull();
+            mObserver = invocation.getArgument(0);
+            return null;
+        }).when(mInjector).registerDisplayListener(any(), any());
+
+        doAnswer(c -> {
+            DisplayInfo info = c.getArgument(1);
+            info.type = Display.TYPE_INTERNAL;
+            info.displayId = DEFAULT_DISPLAY;
+            info.defaultModeId = 0;
+            info.supportedModes = mInternalDisplayModes;
+            info.userPreferredModeId = mInternalDisplayUserPreferredModeId;
+            return true;
+        }).when(mInjector).getDisplayInfo(eq(DEFAULT_DISPLAY), /*displayInfo=*/ any());
+
+        doAnswer(c -> {
+            DisplayInfo info = c.getArgument(1);
+            info.type = Display.TYPE_EXTERNAL;
+            info.displayId = EXTERNAL_DISPLAY;
+            info.defaultModeId = 0;
+            info.supportedModes = mExternalDisplayModes;
+            info.userPreferredModeId = mExternalDisplayUserPreferredModeId;
+            return true;
+        }).when(mInjector).getDisplayInfo(eq(EXTERNAL_DISPLAY), /*displayInfo=*/ any());
+
+        doAnswer(c -> mock(SensorManagerInternal.class)).when(mInjector).getSensorManagerInternal();
+        doAnswer(c -> mock(DeviceConfigInterface.class)).when(mInjector).getDeviceConfig();
+
+        mDefaultDisplay = mock(Display.class);
+        when(mDefaultDisplay.getDisplayId()).thenReturn(DEFAULT_DISPLAY);
+        doAnswer(c -> mInjector.getDisplayInfo(DEFAULT_DISPLAY, c.getArgument(0)))
+                .when(mDefaultDisplay).getDisplayInfo(/*displayInfo=*/ any());
+
+        mExternalDisplay = mock(Display.class);
+        when(mExternalDisplay.getDisplayId()).thenReturn(EXTERNAL_DISPLAY);
+        doAnswer(c -> mInjector.getDisplayInfo(EXTERNAL_DISPLAY, c.getArgument(0)))
+                .when(mExternalDisplay).getDisplayInfo(/*displayInfo=*/ any());
+
+        when(mInjector.getDisplays()).thenReturn(new Display[] {mDefaultDisplay, mExternalDisplay});
+
+        mDmd = new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+        mDmd.start(null);
+        assertThat(mObserver).isNotNull();
+    }
+
+    @Nullable
+    private Vote getVote(final int displayId, final int priority) {
+        return mDmd.getVote(displayId, priority);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
index 287fdd5c344bf9bc90cfa555f43824e9fadcc652..50e239218fa0bd45b6e8632371fd4f35d98ebe25 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
@@ -19,6 +19,7 @@ package com.android.server.display.mode;
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.util.SparseArray;
@@ -72,6 +73,18 @@ public class VotesStorageTest {
         verify(mVotesListener).onChanged();
     }
 
+    /** Verifies that adding the same vote twice results in a single call to onChanged */
+    @Test
+    public void notifiesVoteListenerCalledOnceIfVoteUpdatedTwice() {
+        // WHEN updateVote is called
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE_OTHER);
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE);
+        // THEN listener is notified, but only when vote changes.
+        verify(mVotesListener, times(3)).onChanged();
+    }
+
     @Test
     public void addsAnotherVoteToStorageWithDifferentPriority() {
         // GIVEN vote storage with one vote
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 349a597a37fa59a2b5c49b621353a005f67c1db7..430f600a62889748408ec8c113377b3a3e98ea24 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -687,7 +687,7 @@ public class FullScreenMagnificationGestureHandlerTest {
         swipeAndHold(initCoords, edgeCoords);
 
         assertTrue(mMgh.mCurrentState == mMgh.mSinglePanningState);
-        assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_LEFT_EDGE);
+        assertTrue(mMgh.mOverscrollHandler.mOverscrollState == mMgh.OVERSCROLL_LEFT_EDGE);
         assertTrue(isZoomed());
     }
 
@@ -711,7 +711,7 @@ public class FullScreenMagnificationGestureHandlerTest {
         swipeAndHold(initCoords, edgeCoords);
 
         assertTrue(mMgh.mCurrentState == mMgh.mSinglePanningState);
-        assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_RIGHT_EDGE);
+        assertTrue(mMgh.mOverscrollHandler.mOverscrollState == mMgh.OVERSCROLL_RIGHT_EDGE);
         assertTrue(isZoomed());
     }
 
@@ -734,7 +734,7 @@ public class FullScreenMagnificationGestureHandlerTest {
 
         swipeAndHold(initCoords, edgeCoords);
 
-        assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_VERTICAL_EDGE);
+        assertTrue(mMgh.mOverscrollHandler.mOverscrollState == mMgh.OVERSCROLL_VERTICAL_EDGE);
         assertTrue(isZoomed());
     }
 
@@ -756,7 +756,7 @@ public class FullScreenMagnificationGestureHandlerTest {
 
         swipeAndHold(initCoords, edgeCoords);
 
-        assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_NONE);
+        assertTrue(mMgh.mOverscrollHandler.mOverscrollState == mMgh.OVERSCROLL_NONE);
         assertTrue(isZoomed());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 61c4d06131e16bf489dcddce85ac6a8430ccf0d9..8db09f9e3a16119318fdc32169084000c666ed52 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -46,6 +46,7 @@ import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
 import static java.util.Collections.unmodifiableMap;
 
 import android.content.Context;
+import android.os.Looper;
 import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.view.InputDevice;
@@ -98,6 +99,10 @@ class ShortcutKeyTestBase {
      *      settings values.
      */
     protected final void setUpPhoneWindowManager(boolean supportSettingsUpdate) {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
         doReturn(mSettingsProviderRule.mockContentResolver(mContext))
                 .when(mContext).getContentResolver();
         mPhoneWindowManager = new TestPhoneWindowManager(mContext, supportSettingsUpdate);
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 6e2c4bd7e570943727a61d8d7fd256dc352253e8..ef28ffa7da8fbf5312484b023dd9cb8b0f8e1e61 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -70,6 +70,7 @@ import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputManager;
 import android.media.AudioManagerInternal;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
@@ -77,7 +78,6 @@ import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.Vibrator;
 import android.os.VibratorInfo;
-import android.os.test.TestLooper;
 import android.service.dreams.DreamManagerInternal;
 import android.telecom.TelecomManager;
 import android.util.FeatureFlagUtils;
@@ -160,8 +160,8 @@ class TestPhoneWindowManager {
     @Mock private KeyguardServiceDelegate mKeyguardServiceDelegate;
 
     private StaticMockitoSession mMockitoSession;
+    private HandlerThread mHandlerThread;
     private Handler mHandler;
-    private TestLooper mTestLooper;
 
     private class TestInjector extends PhoneWindowManager.Injector {
         TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) {
@@ -184,11 +184,12 @@ class TestPhoneWindowManager {
 
     TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
         MockitoAnnotations.initMocks(this);
-        mTestLooper = new TestLooper();
-        mHandler = new Handler(mTestLooper.getLooper());
+        mHandlerThread = new HandlerThread("fake window manager");
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
         mContext = mockingDetails(context).isSpy() ? context : spy(context);
-        mHandler.post(() -> setUp(supportSettingsUpdate));
-        mTestLooper.dispatchAll();
+        mHandler.runWithScissors(() -> setUp(supportSettingsUpdate),  0 /* timeout */);
+        waitForIdle();
     }
 
     private void setUp(boolean supportSettingsUpdate) {
@@ -300,6 +301,7 @@ class TestPhoneWindowManager {
     }
 
     void tearDown() {
+        mHandlerThread.quitSafely();
         LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
         Mockito.reset(mPhoneWindowManager);
         mMockitoSession.finishMocking();
@@ -326,7 +328,7 @@ class TestPhoneWindowManager {
     }
 
     void waitForIdle() {
-        mTestLooper.dispatchAll();
+        mHandler.runWithScissors(() -> { }, 0 /* timeout */);
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 0494dfdfb1ec91faf037ce75bd0eee13be867424..f65cb93258bca5fc2ea3e1b793062842f99aee24 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -147,14 +147,12 @@ import android.view.DisplayInfo;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner.Stub;
 import android.view.IWindowManager;
-import android.view.IWindowSession;
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationTarget;
 import android.view.Surface;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 import android.window.TaskSnapshot;
 
 import androidx.test.filters.MediumTest;
@@ -2073,7 +2071,7 @@ public class ActivityRecordTests extends WindowTestsBase {
                 WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
         params.width = params.height = WindowManager.LayoutParams.MATCH_PARENT;
         final TestWindowState w = new TestWindowState(
-                mAtm.mWindowManager, mock(Session.class), new TestIWindow(), params, activity);
+                mAtm.mWindowManager, getTestSession(), new TestIWindow(), params, activity);
         activity.addWindow(w);
 
         // Assume the activity is launching in different rotation, and there was an available
@@ -2083,23 +2081,8 @@ public class ActivityRecordTests extends WindowTestsBase {
                 .build();
         setRotatedScreenOrientationSilently(activity);
         activity.setVisible(false);
-
-        final IWindowSession session = WindowManagerGlobal.getWindowSession();
-        spyOn(session);
-        try {
-            // Return error to skip unnecessary operation.
-            doReturn(WindowManagerGlobal.ADD_STARTING_NOT_NEEDED).when(session).addToDisplay(
-                    any() /* window */,  any() /* attrs */,
-                    anyInt() /* viewVisibility */, anyInt() /* displayId */,
-                    anyInt() /* requestedVisibleTypes */, any() /* outInputChannel */,
-                    any() /* outInsetsState */, any() /* outActiveControls */,
-                    any() /* outAttachedFrame */, any() /* outSizeCompatScale */);
-            mAtm.mWindowManager.mStartingSurfaceController
-                    .createTaskSnapshotSurface(activity, snapshot);
-        } catch (RemoteException ignored) {
-        } finally {
-            reset(session);
-        }
+        mAtm.mWindowManager.mStartingSurfaceController
+                .createTaskSnapshotSurface(activity, snapshot);
 
         // Because the rotation of snapshot and the corresponding top activity are different, fixed
         // rotation should be applied when creating snapshot surface if the display rotation may be
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index f30ecbe96c8b58aa1d6fbec81448bd8d5e1b2443..8e7ba7030e829aa1293b590625a219567fe35649 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -337,6 +337,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
         WindowState appWindow = task.getTopVisibleAppMainWindow();
         WindowOnBackInvokedDispatcher dispatcher =
                 new WindowOnBackInvokedDispatcher(context);
+        spyOn(appWindow.mSession);
         doAnswer(invocation -> {
             appWindow.setOnBackInvokedCallbackInfo(invocation.getArgument(1));
             return null;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 1ad04a254f66ce88cad0ffa13c6d586a30dc70ca..cd0389dce416be6d1016c5bac106170a1e877ae5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -700,7 +700,7 @@ public class DisplayAreaTest extends WindowTestsBase {
     }
 
     private WindowState createWindowState(WindowToken token) {
-        return new WindowState(mWm, mock(Session.class), new TestIWindow(), token,
+        return new WindowState(mWm, getTestSession(), new TestIWindow(), token,
                 null /* parentWindow */, 0 /* appOp */, new WindowManager.LayoutParams(),
                 View.VISIBLE, 0 /* ownerId */, 0 /* showUserId */,
                 false /* ownerCanAddInternalSystemWindow */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index ae4ebc1223b2d440fd8da35c1b31699888656ef8..c2b7fec4ff66e991f76b01b92f654792e5cc8805 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -573,7 +573,10 @@ public class DisplayContentTests extends WindowTestsBase {
         assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
 
         // Add a window to the second display, and it should be focused
-        final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, dc, "window2");
+        final ActivityRecord app2 = new ActivityBuilder(mAtm)
+                .setTask(new TaskBuilder(mSupervisor).setDisplay(dc).build())
+                .setUseProcess(window1.getProcess()).setOnTop(true).build();
+        final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, app2, "window2");
         window2.mActivityRecord.mTargetSdk = targetSdk;
         updateFocusedWindow();
         assertTrue(window2.isFocused());
@@ -1088,7 +1091,7 @@ public class DisplayContentTests extends WindowTestsBase {
 
         assertFalse(dc.getRotationReversionController().isAnyOverrideActive());
         dc.getDisplayRotation().setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED,
-                ROTATION_90);
+                ROTATION_90, /* caller= */ "DisplayContentTests");
         updateAllDisplayContentAndRotation(dc);
         assertEquals(ROTATION_90, dc.getDisplayRotation()
                 .rotationForOrientation(SCREEN_ORIENTATION_UNSPECIFIED, ROTATION_90));
@@ -1107,7 +1110,7 @@ public class DisplayContentTests extends WindowTestsBase {
         assertEquals(ROTATION_90, dc.getDisplayRotation()
                 .rotationForOrientation(SCREEN_ORIENTATION_UNSPECIFIED, ROTATION_0));
         dc.getDisplayRotation().setUserRotation(WindowManagerPolicy.USER_ROTATION_FREE,
-                ROTATION_0);
+                ROTATION_0, /* caller= */ "DisplayContentTests");
     }
 
     @Test
@@ -1134,7 +1137,8 @@ public class DisplayContentTests extends WindowTestsBase {
         dc.getDisplayRotation().setFixedToUserRotation(
                 IWindowManager.FIXED_TO_USER_ROTATION_ENABLED);
         dc.getDisplayRotation().setUserRotation(
-                WindowManagerPolicy.USER_ROTATION_LOCKED, ROTATION_180);
+                WindowManagerPolicy.USER_ROTATION_LOCKED, ROTATION_180,
+                /* caller= */ "DisplayContentTests");
         final int newOrientation = getRotatedOrientation(dc);
 
         final Task task = new TaskBuilder(mSupervisor)
@@ -1174,7 +1178,8 @@ public class DisplayContentTests extends WindowTestsBase {
         dc.getDisplayRotation().setFixedToUserRotation(
                 IWindowManager.FIXED_TO_USER_ROTATION_ENABLED);
         dc.getDisplayRotation().setUserRotation(
-                WindowManagerPolicy.USER_ROTATION_LOCKED, ROTATION_0);
+                WindowManagerPolicy.USER_ROTATION_LOCKED, ROTATION_0,
+                /* caller= */ "DisplayContentTests");
         dc.getDefaultTaskDisplayArea().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         final int newOrientation = getRotatedOrientation(dc);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 915b387ddb75155f5ce41be801703c9437f9d5b6..e14568d281c05b11f3f4cc1b33a8ff5d9dd59b0a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -1265,7 +1265,7 @@ public class DisplayRotationTests {
     }
 
     private void freezeRotation(int rotation) {
-        mTarget.freezeRotation(rotation);
+        mTarget.freezeRotation(rotation, /* caller= */ "DisplayRotationTests");
 
         if (mTarget.isDefaultDisplay) {
             mAccelerometerRotationObserver.onChange(false);
@@ -1274,7 +1274,7 @@ public class DisplayRotationTests {
     }
 
     private void thawRotation() {
-        mTarget.thawRotation();
+        mTarget.thawRotation(/* caller= */ "DisplayRotationTests");
 
         if (mTarget.isDefaultDisplay) {
             mAccelerometerRotationObserver.onChange(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 4526d18d63d8080bb874630f96a4f1878a4be368..50fe0425fe9cbda87b005b218b868db7a8f0bf25 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -55,7 +55,6 @@ import android.os.Parcelable;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.view.DragEvent;
-import android.view.IWindowSessionCallback;
 import android.view.InputChannel;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
@@ -98,6 +97,7 @@ public class DragDropControllerTests extends WindowTestsBase {
     private static final String TEST_PACKAGE = "com.test.package";
 
     private TestDragDropController mTarget;
+    private WindowProcessController mProcess;
     private WindowState mWindow;
     private IBinder mToken;
 
@@ -137,10 +137,9 @@ public class DragDropControllerTests extends WindowTestsBase {
      * Creates a window state which can be used as a drop target.
      */
     private WindowState createDropTargetWindow(String name, int ownerId) {
-        final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
-        final Task rootTask = createTask(mDisplayContent);
-        final Task task = createTaskInRootTask(rootTask, ownerId);
-        task.addChild(activity, 0);
+        final Task task = new TaskBuilder(mSupervisor).setUserId(ownerId).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task)
+                .setUseProcess(mProcess).build();
 
         // Use a new TestIWindow so we don't collect events for other windows
         final WindowState window = createWindow(
@@ -167,6 +166,8 @@ public class DragDropControllerTests extends WindowTestsBase {
     @Before
     public void setUp() throws Exception {
         mTarget = new TestDragDropController(mWm, mWm.mH.getLooper());
+        mProcess = mSystemServicesTestRule.addProcess(TEST_PACKAGE, "testProc",
+                TEST_PID, TEST_UID);
         mWindow = createDropTargetWindow("Drag test window", 0);
         doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
         when(mWm.mInputManager.transferTouchFocus(any(InputChannel.class),
@@ -221,8 +222,6 @@ public class DragDropControllerTests extends WindowTestsBase {
 
     @Test
     public void testPrivateInterceptGlobalDragDropFlagChecksPermission() {
-        spyOn(mWm.mContext);
-
         DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
         WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
         attrs.privateFlags |= PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
@@ -323,10 +322,7 @@ public class DragDropControllerTests extends WindowTestsBase {
 
     @Test
     public void testValidateAppActivityArguments() {
-        final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
-            @Override
-            public void onAnimatorScaleChanged(float scale) {}
-        });
+        final Session session = getTestSession();
         try {
             session.validateAndResolveDragMimeTypeExtras(
                     createClipDataForActivity(mock(PendingIntent.class), null), TEST_UID, TEST_PID,
@@ -364,10 +360,7 @@ public class DragDropControllerTests extends WindowTestsBase {
     public void testValidateAppShortcutArguments() {
         doReturn(PERMISSION_GRANTED).when(mWm.mContext)
                 .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS));
-        final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
-            @Override
-            public void onAnimatorScaleChanged(float scale) {}
-        });
+        final Session session = createTestSession(mAtm);
         try {
             session.validateAndResolveDragMimeTypeExtras(
                     createClipDataForShortcut(null, "test_shortcut_id", mock(UserHandle.class)),
@@ -398,10 +391,7 @@ public class DragDropControllerTests extends WindowTestsBase {
     public void testValidateProfileAppShortcutArguments_notCallingUid() {
         doReturn(PERMISSION_GRANTED).when(mWm.mContext)
                 .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS));
-        final Session session = Mockito.spy(new Session(mWm, new IWindowSessionCallback.Stub() {
-            @Override
-            public void onAnimatorScaleChanged(float scale) {}
-        }));
+        final Session session = createTestSession(mAtm);
         final ShortcutServiceInternal shortcutService = mock(ShortcutServiceInternal.class);
         final Intent[] shortcutIntents = new Intent[1];
         shortcutIntents[0] = new Intent();
@@ -443,10 +433,7 @@ public class DragDropControllerTests extends WindowTestsBase {
     public void testValidateAppTaskArguments() {
         doReturn(PERMISSION_GRANTED).when(mWm.mContext)
                 .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS));
-        final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
-            @Override
-            public void onAnimatorScaleChanged(float scale) {}
-        });
+        final Session session = createTestSession(mAtm);
         try {
             final ClipData clipData = new ClipData(
                     new ClipDescription("drag", new String[] { MIMETYPE_APPLICATION_TASK }),
@@ -462,10 +449,7 @@ public class DragDropControllerTests extends WindowTestsBase {
 
     @Test
     public void testValidateFlags() {
-        final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
-            @Override
-            public void onAnimatorScaleChanged(float scale) {}
-        });
+        final Session session = getTestSession();
         try {
             session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION);
             fail("Expected failure without permission");
@@ -478,10 +462,7 @@ public class DragDropControllerTests extends WindowTestsBase {
     public void testValidateFlagsWithPermission() {
         doReturn(PERMISSION_GRANTED).when(mWm.mContext)
                 .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS));
-        final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
-            @Override
-            public void onAnimatorScaleChanged(float scale) {}
-        });
+        final Session session = createTestSession(mAtm);
         try {
             session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION);
             // Expected pass
@@ -571,7 +552,8 @@ public class DragDropControllerTests extends WindowTestsBase {
 
             assertTrue(mWm.mInputManager.transferTouchFocus(new InputChannel(),
                     new InputChannel(), true /* isDragDrop */));
-            mToken = mTarget.performDrag(0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0, data);
+            mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient,
+                    flag, surface, 0, 0, 0, 0, 0, data);
             assertNotNull(mToken);
 
             r.run();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 8f68c0fa0b7310a3588f8a09cffc2d298524e095..1ceb1a8c7097d762ab69ca6fc2b6e188e231f696 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4739,24 +4739,20 @@ public class SizeCompatTests extends WindowTestsBase {
         assertEquals(new Rect(1050, 0, 1750, 1400), mActivity.getBounds());
     }
 
-    private static WindowState addWindowToActivity(ActivityRecord activity) {
+    private WindowState addWindowToActivity(ActivityRecord activity) {
         final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
         params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
         params.setFitInsetsSides(0);
         params.setFitInsetsTypes(0);
         final TestWindowState w = new TestWindowState(
-                activity.mWmService, mock(Session.class), new TestIWindow(), params, activity);
+                activity.mWmService, getTestSession(), new TestIWindow(), params, activity);
         makeWindowVisible(w);
         w.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
         activity.addWindow(w);
         return w;
     }
 
-    private static TestWindowState addStatusBar(DisplayContent displayContent) {
-        final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
-        doReturn(true).when(displayPolicy).hasStatusBar();
-        displayPolicy.onConfigurationChanged();
-
+    private TestWindowState addStatusBar(DisplayContent displayContent) {
         final TestWindowToken token = createTestWindowToken(
                 TYPE_STATUS_BAR, displayContent);
         final WindowManager.LayoutParams attrs =
@@ -4772,11 +4768,12 @@ public class SizeCompatTests extends WindowTestsBase {
                 new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
         };
         final TestWindowState statusBar = new TestWindowState(
-                displayContent.mWmService, mock(Session.class), new TestIWindow(), attrs, token);
+                displayContent.mWmService, getTestSession(), new TestIWindow(), attrs, token);
         token.addWindow(statusBar);
         statusBar.setRequestedSize(displayContent.mBaseDisplayWidth,
                 SystemBarUtils.getStatusBarHeight(displayContent.getDisplayUiContext()));
 
+        final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
         displayPolicy.addWindowLw(statusBar, attrs);
         displayPolicy.layoutWindowLw(statusBar, null, displayContent.mDisplayFrames);
         return statusBar;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index fb27d6368a7e1b091621ac362b45f939d9a48fd6..0c580697bc4acd61651d0e2e9b26d307d36c0614 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -610,7 +610,7 @@ public class TaskTests extends WindowTestsBase {
         // display.
         final DisplayRotation dr = display.mDisplayContent.getDisplayRotation();
         dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED);
-        dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0);
+        dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0, /* caller= */ "TaskTests");
 
         final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
@@ -642,7 +642,7 @@ public class TaskTests extends WindowTestsBase {
         // Setting app to fixed landscape and changing display
         top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
         // Fix the display orientation to portrait which is 90 degrees for the test display.
-        dr.setUserRotation(USER_ROTATION_FREE, ROTATION_90);
+        dr.setUserRotation(USER_ROTATION_FREE, ROTATION_90, /* caller= */ "TaskTests");
 
         // Fixed orientation request should be resolved on activity level. Task fills display
         // bounds.
@@ -681,7 +681,7 @@ public class TaskTests extends WindowTestsBase {
         // display.
         final DisplayRotation dr = display.mDisplayContent.getDisplayRotation();
         dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED);
-        dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0);
+        dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0, /* caller= */ "TaskTests");
 
         final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 76576f76355d7c648fe32707dc5e4c2df5ffc0bb..e86fc366a6314c593b8c7b277708f3d17ebf093d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -82,7 +82,6 @@ import android.platform.test.annotations.Presubmit;
 import android.util.MergedConfiguration;
 import android.view.ContentRecordingSession;
 import android.view.IWindow;
-import android.view.IWindowSessionCallback;
 import android.view.InputChannel;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
@@ -496,11 +495,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
         spyOn(mWm.mWindowContextListenerController);
 
         final WindowToken windowToken = createTestWindowToken(TYPE_INPUT_METHOD, mDefaultDisplay);
-        final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
-            @Override
-            public void onAnimatorScaleChanged(float v) throws RemoteException {
-            }
-        });
+        final Session session = getTestSession();
         final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                 TYPE_APPLICATION_ATTACHED_DIALOG);
         params.token = windowToken.token;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 0ddd3135506e2a6a0c9be22bded8c8852b1573c5..014d57dd9970d2c14bc71fac9ea5bd1cad7836b5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -95,7 +95,6 @@ import android.util.ArraySet;
 import android.util.MergedConfiguration;
 import android.view.Gravity;
 import android.view.IWindow;
-import android.view.IWindowSessionCallback;
 import android.view.InputWindowHandle;
 import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
@@ -1332,12 +1331,7 @@ public class WindowStateTests extends WindowTestsBase {
         final WindowToken windowToken = createTestWindowToken(TYPE_APPLICATION_OVERLAY,
                 mDisplayContent);
         final IWindow client = new TestIWindow();
-        final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
-            @Override
-            public void onAnimatorScaleChanged(float v) throws RemoteException {
-
-            }
-        });
+        final Session session = getTestSession();
         final ClientWindowFrames outFrames = new ClientWindowFrames();
         final MergedConfiguration outConfig = new MergedConfiguration();
         final SurfaceControl outSurfaceControl = new SurfaceControl();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 99688dabda6e4562bd5ed88b91e81b1dd3f04e3b..9146889e37d9d042b980f39f9342303efec75077 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -94,6 +94,7 @@ import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.IDisplayWindowInsetsController;
 import android.view.IWindow;
+import android.view.IWindowSessionCallback;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
@@ -153,7 +154,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
     ActivityTaskSupervisor mSupervisor;
     WindowManagerService mWm;
     private final IWindow mIWindow = new TestIWindow();
-    private Session mMockSession;
+    private Session mTestSession;
     private boolean mUseFakeSettingsProvider;
 
     DisplayInfo mDisplayInfo = new DisplayInfo();
@@ -231,7 +232,6 @@ class WindowTestsBase extends SystemServiceTestsBase {
         suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget());
 
         mTransaction = mSystemServicesTestRule.mTransaction;
-        mMockSession = mock(Session.class);
 
         mContext.getSystemService(DisplayManager.class)
                 .getDisplay(Display.DEFAULT_DISPLAY).getDisplayInfo(mDisplayInfo);
@@ -508,6 +508,54 @@ class WindowTestsBase extends SystemServiceTestsBase {
         return statusBar;
     }
 
+    Session getTestSession() {
+        if (mTestSession != null) {
+            return mTestSession;
+        }
+        mTestSession = createTestSession(mAtm);
+        return mTestSession;
+    }
+
+    private Session getTestSession(WindowToken token) {
+        final ActivityRecord r = token.asActivityRecord();
+        if (r == null || r.app == null) {
+            return getTestSession();
+        }
+        // If the activity has a process, let the window session belonging to activity use the
+        // process of the activity.
+        int pid = r.app.getPid();
+        if (pid == 0) {
+            // See SystemServicesTestRule#addProcess, pid 0 isn't added to the map. So generate
+            // a non-zero pid to initialize it.
+            final int numPid = mAtm.mProcessMap.getPidMap().size();
+            pid = numPid > 0 ? mAtm.mProcessMap.getPidMap().keyAt(numPid - 1) + 1 : 1;
+            r.app.setPid(pid);
+            mAtm.mProcessMap.put(pid, r.app);
+        } else {
+            final WindowState win = mRootWindowContainer.getWindow(w -> w.getProcess() == r.app);
+            if (win != null) {
+                // Reuse the same Session if there is a window uses the same process.
+                return win.mSession;
+            }
+        }
+        return createTestSession(mAtm, pid, r.getUid());
+    }
+
+    static Session createTestSession(ActivityTaskManagerService atms) {
+        return createTestSession(atms, WindowManagerService.MY_PID, WindowManagerService.MY_UID);
+    }
+
+    static Session createTestSession(ActivityTaskManagerService atms, int pid, int uid) {
+        if (atms.mProcessMap.getProcess(pid) == null) {
+            SystemServicesTestRule.addProcess(atms, "testPkg", "testProc", pid, uid);
+        }
+        return new Session(atms.mWindowManager, new IWindowSessionCallback.Stub() {
+            @Override
+            public void onAnimatorScaleChanged(float scale) {
+            }
+        }, pid, uid);
+    }
+
     WindowState createAppWindow(Task task, int type, String name) {
         final ActivityRecord activity = createNonAttachedActivityRecord(task.getDisplayContent());
         task.addChild(activity, 0);
@@ -587,7 +635,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
     WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
             int ownerId, boolean ownerCanAddInternalSystemWindow, IWindow iwindow) {
         return createWindow(parent, type, token, name, ownerId, UserHandle.getUserId(ownerId),
-                ownerCanAddInternalSystemWindow, mWm, mMockSession, iwindow,
+                ownerCanAddInternalSystemWindow, mWm, getTestSession(token), iwindow,
                 mSystemServicesTestRule.getPowerManagerWrapper());
     }
 
@@ -891,7 +939,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
     TestWindowState createWindowState(WindowManager.LayoutParams attrs, WindowToken token) {
         SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock);
 
-        return new TestWindowState(mWm, mMockSession, mIWindow, attrs, token);
+        return new TestWindowState(mWm, getTestSession(), mIWindow, attrs, token);
     }
 
     /** Creates a {@link DisplayContent} as parts of simulate display info for test. */
@@ -1705,8 +1753,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
                 final WindowState window = WindowTestsBase.createWindow(null,
                         TYPE_APPLICATION_STARTING, activity,
                         "Starting window", 0 /* ownerId */, 0 /* userId*/,
-                        false /* internalWindows */, mWMService, mock(Session.class),
-                        iWindow,
+                        false /* internalWindows */, mWMService, createTestSession(mAtm), iWindow,
                         mPowerManagerWrapper);
                 activity.mStartingWindow = window;
                 mAppWindowMap.put(info.appToken, window);
diff --git a/tests/BinderLeakTest/Android.bp b/tests/BinderLeakTest/Android.bp
new file mode 100644
index 0000000000000000000000000000000000000000..78b0ede76d4e01b15aff2bd67d8d02e01b32adae
--- /dev/null
+++ b/tests/BinderLeakTest/Android.bp
@@ -0,0 +1,40 @@
+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"],
+}
+
+filegroup {
+    name: "binder_leak_test_aidl",
+    srcs: ["**/*.aidl"],
+    path: "aidl",
+}
+
+java_defaults {
+    name: "BinderTest.defaults",
+    srcs: [
+        "**/*.java",
+        ":binder_leak_test_aidl",
+    ],
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "androidx.test.runner",
+    ],
+}
+
+// Built with target_sdk_version: current
+android_test {
+    name: "BinderLeakTest",
+    defaults: ["BinderTest.defaults"],
+}
+
+// Built with target_sdk_version: 33
+android_test {
+    name: "BinderLeakTest_legacy",
+    defaults: ["BinderTest.defaults"],
+    manifest: "AndroidManifest_legacy.xml",
+}
diff --git a/tests/BinderLeakTest/AndroidManifest.xml b/tests/BinderLeakTest/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..756def7ac29de3f7570b9fbd26504c62b69514d5
--- /dev/null
+++ b/tests/BinderLeakTest/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.test.binder">
+    <application>
+        <service
+            android:name=".MyService"
+            android:enabled="true"
+            android:exported="true"
+            android:process=":service">
+        </service>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.test.binder"
+        android:label="Binder leak test">
+    </instrumentation>
+</manifest>
diff --git a/tests/BinderLeakTest/AndroidManifest_legacy.xml b/tests/BinderLeakTest/AndroidManifest_legacy.xml
new file mode 100644
index 0000000000000000000000000000000000000000..03d1dfd1fd8389493036d97cd8df009458d86f39
--- /dev/null
+++ b/tests/BinderLeakTest/AndroidManifest_legacy.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.test.binder">
+    <uses-sdk android:minSdkVersion="33"
+          android:targetSdkVersion="33"
+          android:maxSdkVersion="33" />
+    <application>
+        <service
+            android:name=".MyService"
+            android:enabled="true"
+            android:exported="true"
+            android:process=":service">
+        </service>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.test.binder"
+        android:label="Binder leak test">
+    </instrumentation>
+</manifest>
diff --git a/tests/BinderLeakTest/aidl/com/android/test/binder/IFoo.aidl b/tests/BinderLeakTest/aidl/com/android/test/binder/IFoo.aidl
new file mode 100644
index 0000000000000000000000000000000000000000..a721959d19b4b20448d8f053e9571ce95cbb4cc3
--- /dev/null
+++ b/tests/BinderLeakTest/aidl/com/android/test/binder/IFoo.aidl
@@ -0,0 +1,5 @@
+package com.android.test.binder;
+
+interface IFoo {
+
+}
diff --git a/tests/BinderLeakTest/aidl/com/android/test/binder/IFooProvider.aidl b/tests/BinderLeakTest/aidl/com/android/test/binder/IFooProvider.aidl
new file mode 100644
index 0000000000000000000000000000000000000000..b487f51f812cabf852eaad85c43bda85a0ef0733
--- /dev/null
+++ b/tests/BinderLeakTest/aidl/com/android/test/binder/IFooProvider.aidl
@@ -0,0 +1,10 @@
+package com.android.test.binder;
+import com.android.test.binder.IFoo;
+
+interface IFooProvider {
+    IFoo createFoo();
+
+    boolean isFooGarbageCollected();
+
+    oneway void killProcess();
+}
diff --git a/tests/BinderLeakTest/java/com/android/test/binder/BinderTest.java b/tests/BinderLeakTest/java/com/android/test/binder/BinderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f07317f7d5f3621d4927281a1ac9ee737e65c824
--- /dev/null
+++ b/tests/BinderLeakTest/java/com/android/test/binder/BinderTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.binder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Intent;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.rule.ServiceTestRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidJUnit4.class)
+public class BinderTest {
+    @Rule
+    public final ServiceTestRule serviceRule = new ServiceTestRule();
+
+    @Test
+    public void testDeathRecipientLeaksOrNot()
+            throws RemoteException, TimeoutException, InterruptedException {
+        Intent intent = new Intent(ApplicationProvider.getApplicationContext(), MyService.class);
+        IFooProvider provider = IFooProvider.Stub.asInterface(serviceRule.bindService(intent));
+        FooHolder holder = new FooHolder(provider.createFoo());
+
+        // ref will get enqueued right after holder is finalized for gc.
+        ReferenceQueue<FooHolder> refQueue = new ReferenceQueue<>();
+        PhantomReference<FooHolder> ref = new PhantomReference<>(holder, refQueue);
+
+        DeathRecorder deathRecorder = new DeathRecorder();
+        holder.registerDeathRecorder(deathRecorder);
+
+        if (getSdkVersion() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+            /////////////////////////////////////////////
+            // New behavior
+            //
+            // Reference chain at this moment:
+            // holder --(java strong ref)--> FooHolder
+            // FooHolder.mProxy --(java strong ref)--> IFoo.Proxy
+            // IFoo.Proxy.mRemote --(java strong ref)--> BinderProxy
+            // BinderProxy --(binder ref)--> Foo.Stub
+            // In other words, the variable "holder" is the root of the reference chain.
+
+            // By setting the variable to null, we make FooHolder, IFoo.Proxy, BinderProxy, and even
+            // Foo.Stub unreachable.
+            holder = null;
+
+            // Ensure that the objects are garbage collected
+            forceGc();
+            assertEquals(ref, refQueue.poll());
+            assertTrue(provider.isFooGarbageCollected());
+
+            // The binder has died, but we don't get notified since the death recipient is GC'ed.
+            provider.killProcess();
+            Thread.sleep(1000); // give some time for the service process to die and reaped
+            assertFalse(deathRecorder.deathRecorded);
+        } else {
+            /////////////////////////////////////////////
+            // Legacy behavior
+            //
+            // Reference chain at this moment:
+            // JavaDeathRecipient --(JNI strong ref)--> FooHolder
+            // holder --(java strong ref)--> FooHolder
+            // FooHolder.mProxy --(java strong ref)--> IFoo.Proxy
+            // IFoo.Proxy.mRemote --(java strong ref)--> BinderProxy
+            // BinderProxy --(binder ref)--> Foo.Stub
+            // So, BOTH JavaDeathRecipient and holder are roots of the reference chain.
+
+            // Even if we set holder to null, it doesn't make other objects unreachable; they are
+            // still reachable via the JNI strong ref.
+            holder = null;
+
+            // Check that objects are not garbage collected
+            forceGc();
+            assertNotEquals(ref, refQueue.poll());
+            assertFalse(provider.isFooGarbageCollected());
+
+            // The legacy behavior is getting notified even when there's no reference
+            provider.killProcess();
+            Thread.sleep(1000); // give some time for the service process to die and reaped
+            assertTrue(deathRecorder.deathRecorded);
+        }
+    }
+
+    static class FooHolder implements IBinder.DeathRecipient {
+        private IFoo mProxy;
+        private DeathRecorder mDeathRecorder;
+
+        FooHolder(IFoo proxy) throws RemoteException {
+            proxy.asBinder().linkToDeath(this, 0);
+
+            // A strong reference from DeathRecipient(this) to the binder proxy is created here
+            mProxy = proxy;
+        }
+
+        public void registerDeathRecorder(DeathRecorder dr) {
+            mDeathRecorder = dr;
+        }
+
+        @Override
+        public void binderDied() {
+            if (mDeathRecorder != null) {
+                mDeathRecorder.deathRecorded = true;
+            }
+        }
+    }
+
+    static class DeathRecorder {
+        public boolean deathRecorded = false;
+    }
+
+    // Try calling System.gc() until an orphaned object is confirmed to be finalized
+    private static void forceGc() {
+        Object obj = new Object();
+        ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
+        PhantomReference<Object> ref = new PhantomReference<>(obj, refQueue);
+        obj = null; // make it an orphan
+        while (refQueue.poll() != ref) {
+            System.gc();
+        }
+    }
+
+    private static int getSdkVersion() {
+        return ApplicationProvider.getApplicationContext().getApplicationInfo().targetSdkVersion;
+    }
+}
diff --git a/tests/BinderLeakTest/java/com/android/test/binder/MyService.java b/tests/BinderLeakTest/java/com/android/test/binder/MyService.java
new file mode 100644
index 0000000000000000000000000000000000000000..c701253446f4a1e2a0849d32f5632097340813a2
--- /dev/null
+++ b/tests/BinderLeakTest/java/com/android/test/binder/MyService.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.binder;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+
+public class MyService extends Service {
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new IFooProvider.Stub() {
+            ReferenceQueue<IFoo> mRefQueue = new ReferenceQueue<>();
+            PhantomReference<IFoo> mRef;
+
+            @Override
+            public IFoo createFoo() throws RemoteException {
+                IFoo binder = new IFoo.Stub() {};
+                mRef = new PhantomReference<>(binder, mRefQueue);
+                return binder;
+            }
+
+            @Override
+            public boolean isFooGarbageCollected() throws RemoteException {
+                forceGc();
+                return mRefQueue.poll() == mRef;
+            }
+
+            @Override
+            public void killProcess() throws RemoteException {
+                android.os.Process.killProcess(android.os.Process.myPid());
+            }
+        };
+    }
+
+    private static void forceGc() {
+        Object obj = new Object();
+        ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
+        PhantomReference<Object> ref = new PhantomReference<>(obj, refQueue);
+        obj = null; // make it an orphan
+        while (refQueue.poll() != ref) {
+            System.gc();
+        }
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index ee22d0732eacc3afa3dcaca73cd9034f10c5e5e1..badd7c8c712c73dc99c06fcdfe2158c46167ce31 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -50,7 +50,6 @@ import org.junit.runners.Parameterized
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 298544839)
 class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
@@ -92,6 +91,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes
      * Checks that the transition starts with [testApp1]'s windows filling/covering exactly the
      * entirety of the display.
      */
+    @FlakyTest(bugId = 298544839)
     @Test
     open fun startsWithApp1WindowsCoverFullScreen() {
         flicker.assertWmStart {
@@ -120,6 +120,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes
      * Checks that [testApp2] windows fill the entire screen (i.e. is "fullscreen") at the end of
      * the transition once we have fully quick switched from [testApp1] back to the [testApp2].
      */
+    @FlakyTest(bugId = 298544839)
     @Test
     open fun endsWithApp2WindowsCoveringFullScreen() {
         flicker.assertWmEnd { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
@@ -129,6 +130,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes
      * Checks that [testApp2] layers fill the entire screen (i.e. is "fullscreen") at the end of the
      * transition once we have fully quick switched from [testApp1] back to the [testApp2].
      */
+    @FlakyTest(bugId = 298544839)
     @Test
     open fun endsWithApp2LayersCoveringFullScreen() {
         flicker.assertLayersEnd {
@@ -141,6 +143,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes
      * Checks that [testApp2] is the top window at the end of the transition once we have fully
      * quick switched from [testApp1] back to the [testApp2].
      */
+    @FlakyTest(bugId = 298544839)
     @Test
     open fun endsWithApp2BeingOnTop() {
         flicker.assertWmEnd { this.isAppWindowOnTop(testApp2) }
@@ -165,6 +168,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes
      * Checks that [testApp2]'s layer starts off invisible and becomes visible at some point before
      * the end of the transition and then stays visible until the end of the transition.
      */
+    @FlakyTest(bugId = 298544839)
     @Test
     open fun app2LayerBecomesAndStaysVisible() {
         flicker.assertLayers { this.isInvisible(testApp2).then().isVisible(testApp2) }
@@ -174,6 +178,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes
      * Checks that [testApp1]'s window starts off visible and becomes invisible at some point before
      * the end of the transition and then stays invisible until the end of the transition.
      */
+    @FlakyTest(bugId = 298544839)
     @Test
     open fun app1WindowBecomesAndStaysInvisible() {
         flicker.assertWm { this.isAppWindowVisible(testApp1).then().isAppWindowInvisible(testApp1) }
@@ -193,6 +198,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes
      * Ensures that at any point, either [testApp2] or [testApp1]'s windows are at least partially
      * visible.
      */
+    @FlakyTest(bugId = 298544839)
     @Test
     open fun app2WindowIsVisibleOnceApp1WindowIsInvisible() {
         flicker.assertWm {
@@ -211,6 +217,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes
      * Ensures that at any point, either [testApp2] or [testApp1]'s windows are at least partially
      * visible.
      */
+    @FlakyTest(bugId = 298544839)
     @Test
     open fun app2LayerIsVisibleOnceApp1LayerIsInvisible() {
         flicker.assertLayers {
@@ -225,6 +232,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes
     }
 
     /** {@inheritDoc} */
+    @FlakyTest(bugId = 298544839)
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
@@ -233,31 +241,45 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes
     @Test
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
+    @FlakyTest(bugId = 298544839)
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
-    @Test override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+    @FlakyTest(bugId = 298544839)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
-    @Test override fun entireScreenCovered() = super.entireScreenCovered()
+    @FlakyTest(bugId = 298544839)
+    @Test
+    override fun entireScreenCovered() = super.entireScreenCovered()
 
+    @FlakyTest(bugId = 298544839)
     @Test
     override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
+    @FlakyTest(bugId = 298544839)
     @Test
     override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd()
 
+    @FlakyTest(bugId = 298544839)
     @Test
     override fun statusBarLayerIsVisibleAtStartAndEnd() =
         super.statusBarLayerIsVisibleAtStartAndEnd()
 
+    @FlakyTest(bugId = 298544839)
     @Test
     override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
-    @Test override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+    @FlakyTest(bugId = 298544839)
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
-    @Test override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+    @FlakyTest(bugId = 298544839)
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
+    @FlakyTest(bugId = 298544839)
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index 06cbeb5368a59a918fec402bd2d44d04a614b5b5..330bc84d7a76c6a8282bd08bb89c7d92b03469bc 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -124,7 +124,7 @@ public class WindowManagerPermissionTests extends TestCase {
     @SmallTest
     public void testSET_ORIENTATION() {
         try {
-            mWm.freezeRotation(-1);
+            mWm.freezeRotation(/* rotation= */ -1, /* caller= */ "WindowManagerPermissionTests");
             fail("IWindowManager.freezeRotation did not throw SecurityException as"
                     + " expected");
         } catch (SecurityException e) {
@@ -134,7 +134,7 @@ public class WindowManagerPermissionTests extends TestCase {
         }
 
         try {
-            mWm.thawRotation();
+            mWm.thawRotation(/* called= */ "WindowManagerPermissionTests");
             fail("IWindowManager.thawRotation did not throw SecurityException as"
                     + " expected");
         } catch (SecurityException e) {
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 1aa485963f9746cdca98c95c5bdaab35a4600ee5..0c1d88ad0ff9cf177635b1bbb4acefd64b4fc716 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -13,11 +13,11 @@ public interface android.hosttest.annotation.HostSideTestClassLoadHook extends j
 }
 SourceFile: "HostSideTestClassLoadHook.java"
 RuntimeVisibleAnnotations:
-  0: #x(#x=[e#x.#x])
+  x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE]
     )
-  1: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -33,11 +33,11 @@ public interface android.hosttest.annotation.HostSideTestKeep extends java.lang.
 }
 SourceFile: "HostSideTestKeep.java"
 RuntimeVisibleAnnotations:
-  0: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
     )
-  1: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -56,11 +56,11 @@ public interface android.hosttest.annotation.HostSideTestNativeSubstitutionClass
 }
 SourceFile: "HostSideTestNativeSubstitutionClass.java"
 RuntimeVisibleAnnotations:
-  0: #x(#x=[e#x.#x])
+  x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE]
     )
-  1: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -76,11 +76,11 @@ public interface android.hosttest.annotation.HostSideTestRemove extends java.lan
 }
 SourceFile: "HostSideTestRemove.java"
 RuntimeVisibleAnnotations:
-  0: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
     )
-  1: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -96,11 +96,11 @@ public interface android.hosttest.annotation.HostSideTestStub extends java.lang.
 }
 SourceFile: "HostSideTestStub.java"
 RuntimeVisibleAnnotations:
-  0: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
     )
-  1: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -119,11 +119,11 @@ public interface android.hosttest.annotation.HostSideTestSubstitute extends java
 }
 SourceFile: "HostSideTestSubstitute.java"
 RuntimeVisibleAnnotations:
-  0: #x(#x=[e#x.#x])
+  x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.METHOD]
     )
-  1: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -139,11 +139,11 @@ public interface android.hosttest.annotation.HostSideTestThrow extends java.lang
 }
 SourceFile: "HostSideTestThrow.java"
 RuntimeVisibleAnnotations:
-  0: #x(#x=[e#x.#x,e#x.#x])
+  x: #x(#x=[e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
     )
-  1: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -159,11 +159,11 @@ public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends
 }
 SourceFile: "HostSideTestWholeClassKeep.java"
 RuntimeVisibleAnnotations:
-  0: #x(#x=[e#x.#x])
+  x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE]
     )
-  1: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -179,11 +179,11 @@ public interface android.hosttest.annotation.HostSideTestWholeClassStub extends
 }
 SourceFile: "HostSideTestWholeClassStub.java"
 RuntimeVisibleAnnotations:
-  0: #x(#x=[e#x.#x])
+  x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE]
     )
-  1: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -199,7 +199,7 @@ public interface android.hosttest.annotation.tests.HostSideTestSuppress extends
 }
 SourceFile: "HostSideTestSuppress.java"
 RuntimeVisibleAnnotations:
-  0: #x(#x=[e#x.#x,e#x.#x,e#x.#x])
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD]
     )
@@ -217,9 +217,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
     flags: (0x0002) ACC_PRIVATE
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -230,11 +230,11 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=1, locals=0, args_size=0
-         0: iconst_1
-         1: ireturn
+         x: iconst_1
+         x: ireturn
       LineNumberTable:
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestKeep
 
   public static int getOneStub();
@@ -242,11 +242,11 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=1, locals=0, args_size=0
-         0: iconst_1
-         1: ireturn
+         x: iconst_1
+         x: ireturn
       LineNumberTable:
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
 }
 SourceFile: "TinyFrameworkCallerCheck.java"
@@ -267,9 +267,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -280,8 +280,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=1, locals=0, args_size=0
-         0: invokestatic  #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I
-         3: ireturn
+         x: invokestatic  #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I
+         x: ireturn
       LineNumberTable:
 
   public static int getOne_noCheck();
@@ -289,13 +289,13 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=1, locals=0, args_size=0
-         0: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I
-         3: ireturn
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I
+         x: ireturn
       LineNumberTable:
 }
 SourceFile: "TinyFrameworkCallerCheck.java"
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 NestMembers:
   com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
@@ -314,14 +314,14 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
 
   public int keep;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestKeep
 
   public int remove;
@@ -333,21 +333,21 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: aload_0
-         5: iconst_1
-         6: putfield      #x                  // Field stub:I
-         9: aload_0
-        10: iconst_2
-        11: putfield      #x                 // Field keep:I
-        14: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_1
+         x: putfield      #x                  // Field stub:I
+         x: aload_0
+        x: iconst_2
+        x: putfield      #x                 // Field keep:I
+        x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
             0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
 
   public int addOne(int);
@@ -355,17 +355,17 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: iload_1
-         2: invokevirtual #x                 // Method addOneInner:(I)I
-         5: ireturn
+         x: aload_0
+         x: iload_1
+         x: invokevirtual #x                 // Method addOneInner:(I)I
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
             0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
             0       6     1 value   I
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
 
   public int addOneInner(int);
@@ -373,17 +373,17 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: iload_1
-         1: iconst_1
-         2: iadd
-         3: ireturn
+         x: iload_1
+         x: iconst_1
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
             0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
             0       4     1 value   I
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestKeep
 
   public void toBeRemoved(java.lang.String);
@@ -391,17 +391,17 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
-         7: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
             0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
             0       8     1   foo   Ljava/lang/String;
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestRemove
 
   public int addTwo(int);
@@ -409,20 +409,20 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String not supported on host side
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String not supported on host side
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
             0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
             0      10     1 value   I
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
-      1: #x(#x=s#x)
+      x: #x(#x=s#x)
         android.hosttest.annotation.HostSideTestSubstitute(
           suffix="_host"
         )
@@ -432,10 +432,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: iload_1
-         1: iconst_2
-         2: iadd
-         3: ireturn
+         x: iload_1
+         x: iconst_2
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -446,9 +446,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     descriptor: (I)I
     flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
-      1: #x(#x=s#x)
+      x: #x(#x=s#x)
         android.hosttest.annotation.HostSideTestSubstitute(
           suffix="_host"
         )
@@ -458,10 +458,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=2, locals=1, args_size=1
-         0: iload_0
-         1: iconst_3
-         2: iadd
-         3: ireturn
+         x: iload_0
+         x: iconst_3
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -472,14 +472,14 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: ldc           #x                 // String This value shouldn\'t be seen on the host side.
-         2: areturn
+         x: ldc           #x                 // String This value shouldn\'t be seen on the host side.
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
             0       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestThrow
 
   public java.lang.String visibleButUsesUnsupportedMethod();
@@ -487,22 +487,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
-         4: areturn
+         x: aload_0
+         x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
 }
 SourceFile: "TinyFrameworkClassAnnotations.java"
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestStub
-  1: #x(#x=s#x)
+  x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestClassLoadHook(
       value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
     )
@@ -532,15 +532,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: aload_0
-         5: iconst_1
-         6: putfield      #x                  // Field stub:I
-         9: aload_0
-        10: iconst_2
-        11: putfield      #x                 // Field keep:I
-        14: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_1
+         x: putfield      #x                  // Field stub:I
+         x: aload_0
+        x: iconst_2
+        x: putfield      #x                 // Field keep:I
+        x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -551,10 +551,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: iload_1
-         2: invokevirtual #x                 // Method addOneInner:(I)I
-         5: ireturn
+         x: aload_0
+         x: iload_1
+         x: invokevirtual #x                 // Method addOneInner:(I)I
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -566,10 +566,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: iload_1
-         1: iconst_1
-         2: iadd
-         3: ireturn
+         x: iload_1
+         x: iconst_1
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -581,10 +581,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
-         7: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -596,20 +596,20 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String not supported on host side
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String not supported on host side
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
             0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
             0      10     1 value   I
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
-      1: #x(#x=s#x)
+      x: #x(#x=s#x)
         android.hosttest.annotation.HostSideTestSubstitute(
           suffix="_host"
         )
@@ -619,10 +619,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: iload_1
-         1: iconst_2
-         2: iadd
-         3: ireturn
+         x: iload_1
+         x: iconst_2
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -633,9 +633,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     descriptor: (I)I
     flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
-      1: #x(#x=s#x)
+      x: #x(#x=s#x)
         android.hosttest.annotation.HostSideTestSubstitute(
           suffix="_host"
         )
@@ -645,10 +645,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=2, locals=1, args_size=1
-         0: iload_0
-         1: iconst_3
-         2: iadd
-         3: ireturn
+         x: iload_0
+         x: iconst_3
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -659,8 +659,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: ldc           #x                 // String This value shouldn\'t be seen on the host side.
-         2: areturn
+         x: ldc           #x                 // String This value shouldn\'t be seen on the host side.
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -671,9 +671,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
-         4: areturn
+         x: aload_0
+         x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -681,7 +681,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
 }
 SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
   Compiled from "TinyFrameworkClassLoadHook.java"
@@ -702,9 +702,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo
     flags: (0x0002) ACC_PRIVATE
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -715,11 +715,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=2, locals=1, args_size=1
-         0: getstatic     #x                  // Field sLoadedClasses:Ljava/util/Set;
-         3: aload_0
-         4: invokeinterface #x,  2           // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z
-         9: pop
-        10: return
+         x: getstatic     #x                  // Field sLoadedClasses:Ljava/util/Set;
+         x: aload_0
+         x: invokeinterface #x,  2           // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z
+         x: pop
+        x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -734,16 +734,16 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo
     flags: (0x0008) ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         0: new           #x                 // class java/util/HashSet
-         3: dup
-         4: invokespecial #x                 // Method java/util/HashSet."<init>":()V
-         7: putstatic     #x                  // Field sLoadedClasses:Ljava/util/Set;
-        10: return
+         x: new           #x                 // class java/util/HashSet
+         x: dup
+         x: invokespecial #x                 // Method java/util/HashSet."<init>":()V
+         x: putstatic     #x                  // Field sLoadedClasses:Ljava/util/Set;
+        x: return
       LineNumberTable:
 }
 SourceFile: "TinyFrameworkClassLoadHook.java"
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.class
   Compiled from "TinyFrameworkClassWithInitializer.java"
@@ -763,9 +763,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -776,18 +776,18 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn
     flags: (0x0008) ACC_STATIC
     Code:
       stack=1, locals=0, args_size=0
-         0: iconst_1
-         1: putstatic     #x                  // Field sInitialized:Z
-         4: return
+         x: iconst_1
+         x: putstatic     #x                  // Field sInitialized:Z
+         x: return
       LineNumberTable:
 }
 SourceFile: "TinyFrameworkClassWithInitializer.java"
 RuntimeInvisibleAnnotations:
-  0: #x(#x=s#x)
+  x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestClassLoadHook(
       value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
     )
-  1: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
   Compiled from "TinyFrameworkExceptionTester.java"
@@ -803,9 +803,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -816,18 +816,18 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=4, locals=1, args_size=0
-         0: new           #x                  // class java/lang/IllegalStateException
-         3: dup
-         4: ldc           #x                  // String Inner exception
-         6: invokespecial #x                 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
-         9: athrow
-        10: astore_0
-        11: new           #x                 // class java/lang/RuntimeException
-        14: dup
-        15: ldc           #x                 // String Outer exception
-        17: aload_0
-        18: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V
-        21: athrow
+         x: new           #x                  // class java/lang/IllegalStateException
+         x: dup
+         x: ldc           #x                  // String Inner exception
+         x: invokespecial #x                 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
+         x: athrow
+        x: astore_0
+        x: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String Outer exception
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V
+        x: athrow
       Exception table:
          from    to  target type
              0    10    10   Class java/lang/Exception
@@ -841,7 +841,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe
 }
 SourceFile: "TinyFrameworkExceptionTester.java"
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
   Compiled from "TinyFrameworkForTextPolicy.java"
@@ -869,15 +869,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: aload_0
-         5: iconst_1
-         6: putfield      #x                  // Field stub:I
-         9: aload_0
-        10: iconst_2
-        11: putfield      #x                 // Field keep:I
-        14: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_1
+         x: putfield      #x                  // Field stub:I
+         x: aload_0
+        x: iconst_2
+        x: putfield      #x                 // Field keep:I
+        x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -888,10 +888,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: iload_1
-         2: invokevirtual #x                 // Method addOneInner:(I)I
-         5: ireturn
+         x: aload_0
+         x: iload_1
+         x: invokevirtual #x                 // Method addOneInner:(I)I
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -903,10 +903,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: iload_1
-         1: iconst_1
-         2: iadd
-         3: ireturn
+         x: iload_1
+         x: iconst_1
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -918,10 +918,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
-         7: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -933,11 +933,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String not supported on host side
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String not supported on host side
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -949,10 +949,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: iload_1
-         1: iconst_2
-         2: iadd
-         3: ireturn
+         x: iload_1
+         x: iconst_2
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -968,10 +968,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=2, locals=1, args_size=1
-         0: iload_0
-         1: iconst_3
-         2: iadd
-         3: ireturn
+         x: iload_0
+         x: iconst_3
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -982,8 +982,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: ldc           #x                 // String This value shouldn\'t be seen on the host side.
-         2: areturn
+         x: ldc           #x                 // String This value shouldn\'t be seen on the host side.
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -994,9 +994,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
-         4: areturn
+         x: aload_0
+         x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1017,9 +1017,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1034,9 +1034,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=1, locals=1, args_size=1
-         0: iload_0
-         1: invokestatic  #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
-         4: ireturn
+         x: iload_0
+         x: invokestatic  #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1051,10 +1051,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=4, locals=4, args_size=2
-         0: lload_0
-         1: lload_2
-         2: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
-         5: lreturn
+         x: lload_0
+         x: lload_2
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
+         x: lreturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1063,9 +1063,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
-  1: #x(#x=s#x)
+  x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestNativeSubstitutionClass(
       value="TinyFrameworkNative_host"
     )
@@ -1083,9 +1083,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1096,10 +1096,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=2, locals=1, args_size=1
-         0: iload_0
-         1: iconst_2
-         2: iadd
-         3: ireturn
+         x: iload_0
+         x: iconst_2
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1110,10 +1110,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=4, locals=4, args_size=2
-         0: lload_0
-         1: lload_2
-         2: ladd
-         3: lreturn
+         x: lload_0
+         x: lload_2
+         x: ladd
+         x: lreturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1122,7 +1122,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
 }
 SourceFile: "TinyFrameworkNative_host.java"
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -1142,12 +1142,12 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex
     flags: (0x0000)
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: aload_1
-         2: putfield      #x                  // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
-         5: aload_0
-         6: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         9: return
+         x: aload_0
+         x: aload_1
+         x: putfield      #x                  // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1159,9 +1159,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: iconst_1
-         1: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
-         4: areturn
+         x: iconst_1
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1172,9 +1172,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex
     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
-         4: areturn
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1200,9 +1200,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex
     flags: (0x0000)
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1213,9 +1213,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: iconst_2
-         1: invokestatic  #x                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
-         4: areturn
+         x: iconst_2
+         x: invokestatic  #x                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1226,9 +1226,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex
     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
-         4: areturn
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1258,12 +1258,12 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex
     flags: (0x0000)
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: aload_1
-         2: putfield      #x                  // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
-         5: aload_0
-         6: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         9: return
+         x: aload_0
+         x: aload_1
+         x: putfield      #x                  // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1275,9 +1275,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: iconst_3
-         1: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
-         4: areturn
+         x: iconst_3
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1288,9 +1288,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex
     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
-         4: areturn
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1316,9 +1316,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex
     flags: (0x0000)
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1329,9 +1329,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: iconst_4
-         1: invokestatic  #x                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
-         4: areturn
+         x: iconst_4
+         x: invokestatic  #x                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1342,9 +1342,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex
     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
-         4: areturn
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1374,12 +1374,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: aload_0
-         5: iload_1
-         6: putfield      #x                  // Field value:I
-         9: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iload_1
+         x: putfield      #x                  // Field value:I
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1412,15 +1412,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: aload_1
-         2: putfield      #x                  // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
-         5: aload_0
-         6: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         9: aload_0
-        10: iconst_5
-        11: putfield      #x                 // Field value:I
-        14: return
+         x: aload_0
+         x: aload_1
+         x: putfield      #x                  // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+        x: iconst_5
+        x: putfield      #x                 // Field value:I
+        x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1429,7 +1429,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
 }
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 InnerClasses:
@@ -1448,9 +1448,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat
     flags: (0x0000)
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1461,9 +1461,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: bipush        7
-         2: invokestatic  #x                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
-         5: areturn
+         x: bipush        7
+         x: invokestatic  #x                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1474,9 +1474,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat
     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
-         4: areturn
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1507,12 +1507,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: aload_0
-         5: bipush        6
-         7: putfield      #x                  // Field value:I
-        10: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: bipush        6
+         x: putfield      #x                  // Field value:I
+        x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1523,16 +1523,16 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
-         3: dup
-         4: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1."<init>":()V
-         7: areturn
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         x: dup
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1."<init>":()V
+         x: areturn
       LineNumberTable:
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
 }
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 InnerClasses:
@@ -1552,10 +1552,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: iload_1
-         2: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass."<init>":(I)V
-         5: return
+         x: aload_0
+         x: iload_1
+         x: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass."<init>":(I)V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1591,15 +1591,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         4: aload_0
-         5: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
-         8: dup
-         9: aload_0
-        10: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
-        13: putfield      #x                 // Field mSupplier:Ljava/util/function/Supplier;
-        16: return
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         x: dup
+         x: aload_0
+        x: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+        x: putfield      #x                 // Field mSupplier:Ljava/util/function/Supplier;
+        x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1610,11 +1610,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
-         3: dup
-         4: aload_0
-         5: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
-         8: areturn
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         x: dup
+         x: aload_0
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1626,10 +1626,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
-         3: dup
-         4: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4."<init>":()V
-         7: areturn
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         x: dup
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4."<init>":()V
+         x: areturn
       LineNumberTable:
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
 
@@ -1638,16 +1638,16 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0008) ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
-         3: dup
-         4: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2."<init>":()V
-         7: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
-        10: return
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         x: dup
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2."<init>":()V
+         x: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
+        x: return
       LineNumberTable:
 }
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 NestMembers:
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index 6e1528aedc1ec47d7470fec599884cd5219dc90e..43ceec42660defccb3b1360914dc595400d07564 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -12,33 +12,33 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
     flags: (0x0002) ACC_PRIVATE
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public static int getOneStub();
     descriptor: ()I
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=3, locals=0, args_size=0
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
 }
 InnerClasses:
   private static #x= #x of #x;           // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
 SourceFile: "TinyFrameworkCallerCheck.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class
@@ -55,44 +55,44 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public static int getOne_withCheck();
     descriptor: ()I
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=3, locals=0, args_size=0
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public static int getOne_noCheck();
     descriptor: ()I
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=3, locals=0, args_size=0
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 }
 InnerClasses:
   private static #x= #x of #x;          // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
 SourceFile: "TinyFrameworkCallerCheck.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 NestMembers:
   com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
@@ -109,7 +109,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
@@ -117,13 +117,13 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
 
   public int addOne(int);
@@ -131,13 +131,13 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
 
   public int addTwo(int);
@@ -145,47 +145,47 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public static int nativeAddThree(int);
     descriptor: (I)I
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public java.lang.String visibleButUsesUnsupportedMethod();
     descriptor: ()Ljava/lang/String;
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
 }
 SourceFile: "TinyFrameworkClassAnnotations.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestStub
-  1: #x(#x=s#x)
+  x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestClassLoadHook(
       value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
     )
@@ -215,97 +215,97 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public int addOne(int);
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public int addOneInner(int);
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public void toBeRemoved(java.lang.String);
     descriptor: (Ljava/lang/String;)V
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public int addTwo(int);
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public static int nativeAddThree(int);
     descriptor: (I)I
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public java.lang.String unsupportedMethod();
     descriptor: ()Ljava/lang/String;
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public java.lang.String visibleButUsesUnsupportedMethod();
     descriptor: ()Ljava/lang/String;
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 }
 SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
   Compiled from "TinyFrameworkClassLoadHook.java"
@@ -326,22 +326,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo
     flags: (0x0002) ACC_PRIVATE
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public static void onClassLoaded(java.lang.Class<?>);
     descriptor: (Ljava/lang/Class;)V
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
     Signature: #x                          // (Ljava/lang/Class<*>;)V
 
   static {};
@@ -349,20 +349,20 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo
     flags: (0x0008) ACC_STATIC
     Code:
       stack=3, locals=0, args_size=0
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 }
 SourceFile: "TinyFrameworkClassLoadHook.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.class
   Compiled from "TinyFrameworkClassWithInitializer.java"
@@ -382,35 +382,35 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   static {};
     descriptor: ()V
     flags: (0x0008) ACC_STATIC
     Code:
       stack=3, locals=0, args_size=0
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 }
 SourceFile: "TinyFrameworkClassWithInitializer.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x(#x=s#x)
+  x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestClassLoadHook(
       value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
     )
-  1: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
   Compiled from "TinyFrameworkExceptionTester.java"
@@ -426,31 +426,31 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public static int testException();
     descriptor: ()I
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=3, locals=0, args_size=0
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 }
 SourceFile: "TinyFrameworkExceptionTester.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
   Compiled from "TinyFrameworkForTextPolicy.java"
@@ -470,61 +470,61 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public int addOne(int);
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public int addTwo(int);
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public static int nativeAddThree(int);
     descriptor: (I)I
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public java.lang.String visibleButUsesUnsupportedMethod();
     descriptor: ()Ljava/lang/String;
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 }
 SourceFile: "TinyFrameworkForTextPolicy.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
   Compiled from "TinyFrameworkNative.java"
@@ -540,11 +540,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public static native int nativeAddTwo(int);
     descriptor: (I)I
@@ -555,11 +555,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public static native long nativeLongPlus(long, long);
     descriptor: (JJ)J
@@ -570,22 +570,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=3, locals=4, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
-  1: #x(#x=s#x)
+  x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestNativeSubstitutionClass(
       value="TinyFrameworkNative_host"
     )
@@ -607,19 +607,19 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 }
 InnerClasses:
   public static #x= #x of #x;            // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class
@@ -644,22 +644,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 }
 InnerClasses:
   public #x= #x of #x;                   // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
@@ -680,22 +680,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
     descriptor: ()Ljava/util/function/Supplier;
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=3, locals=0, args_size=0
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
 }
 InnerClasses:
@@ -703,12 +703,12 @@ InnerClasses:
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
@@ -725,20 +725,20 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 }
 InnerClasses:
   public static #x= #x of #x;            // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   public static #x= #x of #x;            // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class
@@ -765,22 +765,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 
   public java.util.function.Supplier<java.lang.Integer> getSupplier();
     descriptor: ()Ljava/util/function/Supplier;
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
 
   public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
@@ -788,11 +788,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=3, locals=0, args_size=0
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
 
   static {};
@@ -800,11 +800,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0008) ACC_STATIC
     Code:
       stack=3, locals=0, args_size=0
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: ldc           #x                 // String Stub!
-         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         9: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
 }
 InnerClasses:
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
@@ -818,12 +818,12 @@ InnerClasses:
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 NestMembers:
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index 5672e9c367063aa80b902639c65a66c2d2fa86de..faf0a46c8fabc9ecb5e7e20ebc48d911b6b50291 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -13,13 +13,13 @@ public interface android.hosttest.annotation.HostSideTestClassLoadHook extends j
 }
 SourceFile: "HostSideTestClassLoadHook.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
-  1: #x(#x=[e#x.#x])
+  x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE]
     )
-  2: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -35,13 +35,13 @@ public interface android.hosttest.annotation.HostSideTestKeep extends java.lang.
 }
 SourceFile: "HostSideTestKeep.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
-  1: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
     )
-  2: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -60,13 +60,13 @@ public interface android.hosttest.annotation.HostSideTestNativeSubstitutionClass
 }
 SourceFile: "HostSideTestNativeSubstitutionClass.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
-  1: #x(#x=[e#x.#x])
+  x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE]
     )
-  2: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -82,13 +82,13 @@ public interface android.hosttest.annotation.HostSideTestRemove extends java.lan
 }
 SourceFile: "HostSideTestRemove.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
-  1: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
     )
-  2: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -104,13 +104,13 @@ public interface android.hosttest.annotation.HostSideTestStub extends java.lang.
 }
 SourceFile: "HostSideTestStub.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
-  1: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
     )
-  2: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -129,13 +129,13 @@ public interface android.hosttest.annotation.HostSideTestSubstitute extends java
 }
 SourceFile: "HostSideTestSubstitute.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
-  1: #x(#x=[e#x.#x])
+  x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.METHOD]
     )
-  2: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -151,13 +151,13 @@ public interface android.hosttest.annotation.HostSideTestThrow extends java.lang
 }
 SourceFile: "HostSideTestThrow.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
-  1: #x(#x=[e#x.#x,e#x.#x])
+  x: #x(#x=[e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
     )
-  2: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -173,13 +173,13 @@ public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends
 }
 SourceFile: "HostSideTestWholeClassKeep.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
-  1: #x(#x=[e#x.#x])
+  x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE]
     )
-  2: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -195,13 +195,13 @@ public interface android.hosttest.annotation.HostSideTestWholeClassStub extends
 }
 SourceFile: "HostSideTestWholeClassStub.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
-  1: #x(#x=[e#x.#x])
+  x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE]
     )
-  2: #x(#x=e#x.#x)
+  x: #x(#x=e#x.#x)
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
@@ -219,9 +219,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
     flags: (0x0002) ACC_PRIVATE
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -232,17 +232,17 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=4, locals=0, args_size=0
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-         2: ldc           #x                 // String getOneKeep
-         4: ldc           #x                 // String ()I
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: iconst_1
-        16: ireturn
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+         x: ldc           #x                 // String getOneKeep
+         x: ldc           #x                 // String ()I
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: iconst_1
+        x: ireturn
       LineNumberTable:
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestKeep
 
   public static int getOneStub();
@@ -250,20 +250,20 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=1, locals=0, args_size=0
-         0: iconst_1
-         1: ireturn
+         x: iconst_1
+         x: ireturn
       LineNumberTable:
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
 }
 InnerClasses:
   private static #x= #x of #x;           // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
 SourceFile: "TinyFrameworkCallerCheck.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class
@@ -280,9 +280,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -293,8 +293,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=1, locals=0, args_size=0
-         0: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I
-         3: ireturn
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I
+         x: ireturn
       LineNumberTable:
 
   public static int getOne_noCheck();
@@ -302,20 +302,20 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=1, locals=0, args_size=0
-         0: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I
-         3: ireturn
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I
+         x: ireturn
       LineNumberTable:
 }
 InnerClasses:
   private static #x= #x of #x;          // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
 SourceFile: "TinyFrameworkCallerCheck.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 NestMembers:
   com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
@@ -332,14 +332,14 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
 
   public int keep;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestKeep
 
   private static {};
@@ -347,31 +347,31 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x000a) ACC_PRIVATE, ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         0: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-         2: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
-         4: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
-         7: return
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         4: aload_0
-         5: iconst_1
-         6: putfield      #x                 // Field stub:I
-         9: aload_0
-        10: iconst_2
-        11: putfield      #x                 // Field keep:I
-        14: return
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_1
+         x: putfield      #x                 // Field stub:I
+         x: aload_0
+        x: iconst_2
+        x: putfield      #x                 // Field keep:I
+        x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
             0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
 
   public int addOne(int);
@@ -379,17 +379,17 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: iload_1
-         2: invokevirtual #x                 // Method addOneInner:(I)I
-         5: ireturn
+         x: aload_0
+         x: iload_1
+         x: invokevirtual #x                 // Method addOneInner:(I)I
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
             0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
             0       6     1 value   I
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
 
   public int addOneInner(int);
@@ -397,23 +397,23 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=2, args_size=2
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-         2: ldc           #x                 // String addOneInner
-         4: ldc           #x                 // String (I)I
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: iload_1
-        16: iconst_1
-        17: iadd
-        18: ireturn
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                 // String addOneInner
+         x: ldc           #x                 // String (I)I
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: iload_1
+        x: iconst_1
+        x: iadd
+        x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
            15       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
            15       4     1 value   I
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestKeep
 
   public int addTwo(int);
@@ -421,10 +421,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: iload_1
-         1: iconst_2
-         2: iadd
-         3: ireturn
+         x: iload_1
+         x: iconst_2
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -436,10 +436,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=2, locals=1, args_size=1
-         0: iload_0
-         1: iconst_3
-         2: iadd
-         3: ireturn
+         x: iload_0
+         x: iconst_3
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -450,20 +450,20 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=1, args_size=1
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-         2: ldc           #x                 // String unsupportedMethod
-         4: ldc           #x                 // String ()Ljava/lang/String;
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
-        18: new           #x                 // class java/lang/RuntimeException
-        21: dup
-        22: ldc           #x                 // String Unreachable
-        24: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-        27: athrow
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                 // String unsupportedMethod
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+        x: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String Unreachable
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestThrow
 
   public java.lang.String visibleButUsesUnsupportedMethod();
@@ -471,27 +471,27 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
-         4: areturn
+         x: aload_0
+         x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
     RuntimeInvisibleAnnotations:
-      0: #x()
+      x: #x()
         android.hosttest.annotation.HostSideTestStub
 }
 SourceFile: "TinyFrameworkClassAnnotations.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestStub
-  1: #x(#x=s#x)
+  x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestClassLoadHook(
       value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
     )
@@ -521,15 +521,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         4: aload_0
-         5: iconst_1
-         6: putfield      #x                 // Field stub:I
-         9: aload_0
-        10: iconst_2
-        11: putfield      #x                 // Field keep:I
-        14: return
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_1
+         x: putfield      #x                 // Field stub:I
+         x: aload_0
+        x: iconst_2
+        x: putfield      #x                 // Field keep:I
+        x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -540,10 +540,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: iload_1
-         2: invokevirtual #x                 // Method addOneInner:(I)I
-         5: ireturn
+         x: aload_0
+         x: iload_1
+         x: invokevirtual #x                 // Method addOneInner:(I)I
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -555,10 +555,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: iload_1
-         1: iconst_1
-         2: iadd
-         3: ireturn
+         x: iload_1
+         x: iconst_1
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -570,10 +570,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: new           #x                 // class java/lang/RuntimeException
-         3: dup
-         4: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
-         7: athrow
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -585,10 +585,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: iload_1
-         1: iconst_2
-         2: iadd
-         3: ireturn
+         x: iload_1
+         x: iconst_2
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -600,10 +600,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=2, locals=1, args_size=1
-         0: iload_0
-         1: iconst_3
-         2: iadd
-         3: ireturn
+         x: iload_0
+         x: iconst_3
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -614,8 +614,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: ldc           #x                 // String This value shouldn\'t be seen on the host side.
-         2: areturn
+         x: ldc           #x                 // String This value shouldn\'t be seen on the host side.
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -626,9 +626,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
-         4: areturn
+         x: aload_0
+         x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -636,12 +636,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW
 }
 SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
   Compiled from "TinyFrameworkClassLoadHook.java"
@@ -662,9 +662,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo
     flags: (0x0002) ACC_PRIVATE
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -675,11 +675,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=2, locals=1, args_size=1
-         0: getstatic     #x                 // Field sLoadedClasses:Ljava/util/Set;
-         3: aload_0
-         4: invokeinterface #x,  2           // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z
-         9: pop
-        10: return
+         x: getstatic     #x                 // Field sLoadedClasses:Ljava/util/Set;
+         x: aload_0
+         x: invokeinterface #x,  2           // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z
+         x: pop
+        x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -694,21 +694,21 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo
     flags: (0x0008) ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         0: new           #x                 // class java/util/HashSet
-         3: dup
-         4: invokespecial #x                 // Method java/util/HashSet."<init>":()V
-         7: putstatic     #x                 // Field sLoadedClasses:Ljava/util/Set;
-        10: return
+         x: new           #x                 // class java/util/HashSet
+         x: dup
+         x: invokespecial #x                 // Method java/util/HashSet."<init>":()V
+         x: putstatic     #x                 // Field sLoadedClasses:Ljava/util/Set;
+        x: return
       LineNumberTable:
 }
 SourceFile: "TinyFrameworkClassLoadHook.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.class
   Compiled from "TinyFrameworkClassWithInitializer.java"
@@ -728,9 +728,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -741,26 +741,26 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn
     flags: (0x0008) ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         0: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer
-         2: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
-         4: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
-         7: iconst_1
-         8: putstatic     #x                 // Field sInitialized:Z
-        11: return
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer
+         x: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: iconst_1
+         x: putstatic     #x                 // Field sInitialized:Z
+        x: return
       LineNumberTable:
 }
 SourceFile: "TinyFrameworkClassWithInitializer.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x(#x=s#x)
+  x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestClassLoadHook(
       value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
     )
-  1: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
   Compiled from "TinyFrameworkExceptionTester.java"
@@ -776,9 +776,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -789,18 +789,18 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=4, locals=1, args_size=0
-         0: new           #x                 // class java/lang/IllegalStateException
-         3: dup
-         4: ldc           #x                 // String Inner exception
-         6: invokespecial #x                 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
-         9: athrow
-        10: astore_0
-        11: new           #x                 // class java/lang/RuntimeException
-        14: dup
-        15: ldc           #x                 // String Outer exception
-        17: aload_0
-        18: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V
-        21: athrow
+         x: new           #x                 // class java/lang/IllegalStateException
+         x: dup
+         x: ldc           #x                 // String Inner exception
+         x: invokespecial #x                 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
+         x: athrow
+        x: astore_0
+        x: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String Outer exception
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V
+        x: athrow
       Exception table:
          from    to  target type
              0    10    10   Class java/lang/Exception
@@ -814,12 +814,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe
 }
 SourceFile: "TinyFrameworkExceptionTester.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
   Compiled from "TinyFrameworkForTextPolicy.java"
@@ -843,25 +843,25 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x000a) ACC_PRIVATE, ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         0: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         2: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
-         4: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
-         7: return
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         4: aload_0
-         5: iconst_1
-         6: putfield      #x                 // Field stub:I
-         9: aload_0
-        10: iconst_2
-        11: putfield      #x                 // Field keep:I
-        14: return
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_1
+         x: putfield      #x                 // Field stub:I
+         x: aload_0
+        x: iconst_2
+        x: putfield      #x                 // Field keep:I
+        x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -872,10 +872,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: iload_1
-         2: invokevirtual #x                 // Method addOneInner:(I)I
-         5: ireturn
+         x: aload_0
+         x: iload_1
+         x: invokevirtual #x                 // Method addOneInner:(I)I
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -887,16 +887,16 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=2, args_size=2
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         2: ldc           #x                 // String addOneInner
-         4: ldc           #x                 // String (I)I
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: iload_1
-        16: iconst_1
-        17: iadd
-        18: ireturn
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String addOneInner
+         x: ldc           #x                 // String (I)I
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: iload_1
+        x: iconst_1
+        x: iadd
+        x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -908,10 +908,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: iload_1
-         1: iconst_2
-         2: iadd
-         3: ireturn
+         x: iload_1
+         x: iconst_2
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -923,10 +923,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=2, locals=1, args_size=1
-         0: iload_0
-         1: iconst_3
-         2: iadd
-         3: ireturn
+         x: iload_0
+         x: iconst_3
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -937,27 +937,27 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=1, args_size=1
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         2: ldc           #x                 // String unsupportedMethod
-         4: ldc           #x                 // String ()Ljava/lang/String;
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
-        18: new           #x                 // class java/lang/RuntimeException
-        21: dup
-        22: ldc           #x                 // String Unreachable
-        24: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-        27: athrow
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String unsupportedMethod
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+        x: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String Unreachable
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
 
   public java.lang.String visibleButUsesUnsupportedMethod();
     descriptor: ()Ljava/lang/String;
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
-         4: areturn
+         x: aload_0
+         x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -965,9 +965,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli
 }
 SourceFile: "TinyFrameworkForTextPolicy.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
   Compiled from "TinyFrameworkNative.java"
@@ -983,9 +983,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -996,18 +996,18 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=1, locals=1, args_size=1
-         0: iload_0
-         1: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
-         4: ireturn
+         x: iload_0
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
+         x: ireturn
 
   public static int nativeAddTwo_should_be_like_this(int);
     descriptor: (I)I
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=1, locals=1, args_size=1
-         0: iload_0
-         1: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
-         4: ireturn
+         x: iload_0
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1018,20 +1018,20 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=4, locals=4, args_size=2
-         0: lload_0
-         1: lload_2
-         2: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
-         5: lreturn
+         x: lload_0
+         x: lload_2
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
+         x: lreturn
 
   public static long nativeLongPlus_should_be_like_this(long, long);
     descriptor: (JJ)J
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=4, locals=4, args_size=2
-         0: lload_0
-         1: lload_2
-         2: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
-         5: lreturn
+         x: lload_0
+         x: lload_2
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
+         x: lreturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1040,14 +1040,14 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
-  1: #x(#x=s#x)
+  x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestNativeSubstitutionClass(
       value="TinyFrameworkNative_host"
     )
@@ -1065,15 +1065,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=1, args_size=1
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
-         2: ldc           #x                 // String <init>
-         4: ldc           #x                 // String ()V
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: aload_0
-        16: invokespecial #x                 // Method java/lang/Object."<init>":()V
-        19: return
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1084,16 +1084,16 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=4, locals=1, args_size=1
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
-         2: ldc           #x                 // String nativeAddTwo
-         4: ldc           #x                 // String (I)I
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: iload_0
-        16: iconst_2
-        17: iadd
-        18: ireturn
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String nativeAddTwo
+         x: ldc           #x                 // String (I)I
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: iload_0
+        x: iconst_2
+        x: iadd
+        x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1104,16 +1104,16 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=4, locals=4, args_size=2
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
-         2: ldc           #x                 // String nativeLongPlus
-         4: ldc           #x                 // String (JJ)J
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: lload_0
-        16: lload_2
-        17: ladd
-        18: lreturn
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String nativeLongPlus
+         x: ldc           #x                 // String (JJ)J
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: lload_0
+        x: lload_2
+        x: ladd
+        x: lreturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1122,10 +1122,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
 }
 SourceFile: "TinyFrameworkNative_host.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -1145,12 +1145,12 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex
     flags: (0x0000)
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: aload_1
-         2: putfield      #x                 // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
-         5: aload_0
-         6: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         9: return
+         x: aload_0
+         x: aload_1
+         x: putfield      #x                 // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1162,15 +1162,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=1, args_size=1
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
-         2: ldc           #x                 // String get
-         4: ldc           #x                 // String ()Ljava/lang/Integer;
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: iconst_1
-        16: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
-        19: areturn
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: iconst_1
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1181,15 +1181,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex
     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
     Code:
       stack=4, locals=1, args_size=1
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
-         2: ldc           #x                 // String get
-         4: ldc           #x                 // String ()Ljava/lang/Object;
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: aload_0
-        16: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
-        19: areturn
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Object;
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: aload_0
+        x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+        x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1201,7 +1201,7 @@ EnclosingMethod: #x.#x                 // com.android.hoststubgen.test.tinyframe
 Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2.class
@@ -1218,9 +1218,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex
     flags: (0x0000)
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1231,15 +1231,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=1, args_size=1
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
-         2: ldc           #x                 // String get
-         4: ldc           #x                 // String ()Ljava/lang/Integer;
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: iconst_2
-        16: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
-        19: areturn
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: iconst_2
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1250,15 +1250,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex
     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
     Code:
       stack=4, locals=1, args_size=1
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
-         2: ldc           #x                 // String get
-         4: ldc           #x                 // String ()Ljava/lang/Object;
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: aload_0
-        16: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
-        19: areturn
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Object;
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: aload_0
+        x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+        x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1270,7 +1270,7 @@ EnclosingMethod: #x.#x                 // com.android.hoststubgen.test.tinyframe
 Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3.class
@@ -1291,12 +1291,12 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex
     flags: (0x0000)
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: aload_1
-         2: putfield      #x                 // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
-         5: aload_0
-         6: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         9: return
+         x: aload_0
+         x: aload_1
+         x: putfield      #x                 // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1308,15 +1308,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=1, args_size=1
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
-         2: ldc           #x                 // String get
-         4: ldc           #x                 // String ()Ljava/lang/Integer;
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: iconst_3
-        16: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
-        19: areturn
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: iconst_3
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1327,15 +1327,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex
     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
     Code:
       stack=4, locals=1, args_size=1
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
-         2: ldc           #x                 // String get
-         4: ldc           #x                 // String ()Ljava/lang/Object;
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: aload_0
-        16: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
-        19: areturn
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Object;
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: aload_0
+        x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+        x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1347,7 +1347,7 @@ EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframew
 Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4.class
@@ -1364,9 +1364,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex
     flags: (0x0000)
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1377,15 +1377,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=1, args_size=1
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
-         2: ldc           #x                 // String get
-         4: ldc           #x                 // String ()Ljava/lang/Integer;
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: iconst_4
-        16: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
-        19: areturn
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: iconst_4
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1396,15 +1396,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex
     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
     Code:
       stack=4, locals=1, args_size=1
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
-         2: ldc           #x                 // String get
-         4: ldc           #x                 // String ()Ljava/lang/Object;
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: aload_0
-        16: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
-        19: areturn
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Object;
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: aload_0
+        x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+        x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1416,7 +1416,7 @@ EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframew
 Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class
@@ -1437,12 +1437,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         4: aload_0
-         5: iload_1
-         6: putfield      #x                 // Field value:I
-         9: return
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iload_1
+         x: putfield      #x                 // Field value:I
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1453,9 +1453,9 @@ InnerClasses:
   public static #x= #x of #x;            // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class
@@ -1480,15 +1480,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: aload_1
-         2: putfield      #x                 // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
-         5: aload_0
-         6: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         9: aload_0
-        10: iconst_5
-        11: putfield      #x                 // Field value:I
-        14: return
+         x: aload_0
+         x: aload_1
+         x: putfield      #x                 // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+        x: iconst_5
+        x: putfield      #x                 // Field value:I
+        x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1499,12 +1499,12 @@ InnerClasses:
   public #x= #x of #x;                   // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1.class
@@ -1521,9 +1521,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat
     flags: (0x0000)
     Code:
       stack=1, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         4: return
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1534,15 +1534,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=1, args_size=1
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
-         2: ldc           #x                 // String get
-         4: ldc           #x                 // String ()Ljava/lang/Integer;
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: bipush        7
-        17: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
-        20: areturn
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: bipush        7
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1553,15 +1553,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat
     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
     Code:
       stack=4, locals=1, args_size=1
-         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
-         2: ldc           #x                 // String get
-         4: ldc           #x                 // String ()Ljava/lang/Object;
-         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        15: aload_0
-        16: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
-        19: areturn
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Object;
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: aload_0
+        x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+        x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1574,7 +1574,7 @@ EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframew
 Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
@@ -1595,12 +1595,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         4: aload_0
-         5: bipush        6
-         7: putfield      #x                 // Field value:I
-        10: return
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: bipush        6
+         x: putfield      #x                 // Field value:I
+        x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1611,10 +1611,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
-         3: dup
-         4: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1."<init>":()V
-         7: areturn
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         x: dup
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1."<init>":()V
+         x: areturn
       LineNumberTable:
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
 }
@@ -1623,12 +1623,12 @@ InnerClasses:
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
@@ -1645,10 +1645,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         0: aload_0
-         1: iload_1
-         2: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass."<init>":(I)V
-         5: return
+         x: aload_0
+         x: iload_1
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass."<init>":(I)V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1660,9 +1660,9 @@ InnerClasses:
   public static #x= #x of #x;            // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class
@@ -1689,15 +1689,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=1, args_size=1
-         0: aload_0
-         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         4: aload_0
-         5: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
-         8: dup
-         9: aload_0
-        10: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
-        13: putfield      #x                 // Field mSupplier:Ljava/util/function/Supplier;
-        16: return
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         x: dup
+         x: aload_0
+        x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+        x: putfield      #x                 // Field mSupplier:Ljava/util/function/Supplier;
+        x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1708,11 +1708,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=3, locals=1, args_size=1
-         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
-         3: dup
-         4: aload_0
-         5: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
-         8: areturn
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         x: dup
+         x: aload_0
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
@@ -1724,10 +1724,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
-         3: dup
-         4: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4."<init>":()V
-         7: areturn
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         x: dup
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4."<init>":()V
+         x: areturn
       LineNumberTable:
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
 
@@ -1736,11 +1736,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass
     flags: (0x0008) ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
-         3: dup
-         4: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2."<init>":()V
-         7: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
-        10: return
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         x: dup
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2."<init>":()V
+         x: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
+        x: return
       LineNumberTable:
 }
 InnerClasses:
@@ -1755,12 +1755,12 @@ InnerClasses:
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
-  0: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
-  1: #x()
+  x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 RuntimeInvisibleAnnotations:
-  0: #x()
+  x: #x()
     android.hosttest.annotation.HostSideTestWholeClassStub
 NestMembers:
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
diff --git a/tools/hoststubgen/scripts/dump-jar b/tools/hoststubgen/scripts/dump-jar
index 93729fb22caa6a04994399615a61e5a83ad8f600..992665ed58eeb6f273bb0cd523a78e189f554168 100755
--- a/tools/hoststubgen/scripts/dump-jar
+++ b/tools/hoststubgen/scripts/dump-jar
@@ -93,6 +93,7 @@ filter_output() {
   if (( $simple )) ; then
     # For "simple output" mode,
     # - Normalize the constant numbers (replace with "#x")
+    # - Normalize byte code offsets and other similar numbers. (e.g. "0:" -> "x:")
     # - Remove the constant pool
     # - Remove the line number table
     # - Some other transient lines
@@ -100,6 +101,7 @@ filter_output() {
     # `/PATTERN-1/,/PATTERN-1/{//!d}` is a trick to delete lines between two patterns, without
     # the start and the end lines.
     sed -e 's/#[0-9][0-9]*/#x/g' \
+        -e 's/^\( *\)[0-9][0-9]*:/\1x:/' \
         -e '/^Constant pool:/,/^[^ ]/{//!d}' \
         -e '/^ *line *[0-9][0-9]*: *[0-9][0-9]*$/d' \
         -e '/SHA-256 checksum/d' \