Merge "flag guarding getWindowInsets()" into main
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 56a69b4..e5e0ad3 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -86,6 +86,9 @@
 
 droidstubs {
     name: "system-api-stubs-docs-non-updatable",
+    srcs: [
+        ":framework-minus-apex-aconfig-srcjars",
+    ],
     defaults: [
         "android-non-updatable-stubs-defaults",
         "module-classpath-stubs-defaults",
@@ -126,6 +129,9 @@
 
 droidstubs {
     name: "test-api-stubs-docs-non-updatable",
+    srcs: [
+        ":framework-minus-apex-aconfig-srcjars",
+    ],
     defaults: [
         "android-non-updatable-stubs-defaults",
         "module-classpath-stubs-defaults",
@@ -173,6 +179,9 @@
 
 droidstubs {
     name: "module-lib-api-stubs-docs-non-updatable",
+    srcs: [
+        ":framework-minus-apex-aconfig-srcjars",
+    ],
     defaults: [
         "android-non-updatable-stubs-defaults",
         "module-classpath-stubs-defaults",
diff --git a/core/api/current.txt b/core/api/current.txt
index f640ea2..c9f0639 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6749,7 +6749,6 @@
     method public android.app.Notification.WearableExtender clone();
     method public android.app.Notification.Builder extend(android.app.Notification.Builder);
     method public java.util.List<android.app.Notification.Action> getActions();
-    method @Deprecated public android.graphics.Bitmap getBackground();
     method public String getBridgeTag();
     method public int getContentAction();
     method @Deprecated public int getContentIcon();
@@ -6768,7 +6767,6 @@
     method @Deprecated public boolean getHintShowBackgroundOnly();
     method @Deprecated public java.util.List<android.app.Notification> getPages();
     method public boolean getStartScrollBottom();
-    method @Deprecated public android.app.Notification.WearableExtender setBackground(android.graphics.Bitmap);
     method public android.app.Notification.WearableExtender setBridgeTag(String);
     method public android.app.Notification.WearableExtender setContentAction(int);
     method @Deprecated public android.app.Notification.WearableExtender setContentIcon(int);
@@ -9682,6 +9680,7 @@
     method public int describeContents();
     method public int getDeviceId();
     method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @NonNull public int[] getDisplayIds();
+    method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public CharSequence getDisplayName();
     method @Nullable public String getName();
     method @Nullable public String getPersistentDeviceId();
     method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomSensorSupport();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 9b8d2b4..052d614 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -556,24 +556,24 @@
 
 package android.se.omapi {
 
-  @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public class SeFrameworkInitializer {
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Nullable public static android.se.omapi.SeServiceManager getSeServiceManager();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public static void setSeServiceManager(@NonNull android.se.omapi.SeServiceManager);
+  @FlaggedApi("android.nfc.enable_nfc_mainline") public class SeFrameworkInitializer {
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public static android.se.omapi.SeServiceManager getSeServiceManager();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public static void setSeServiceManager(@NonNull android.se.omapi.SeServiceManager);
   }
 
-  @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public class SeServiceManager {
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.se.omapi.SeServiceManager.ServiceRegisterer getSeManagerServiceRegisterer();
+  @FlaggedApi("android.nfc.enable_nfc_mainline") public class SeServiceManager {
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.se.omapi.SeServiceManager.ServiceRegisterer getSeManagerServiceRegisterer();
   }
 
-  @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public static class SeServiceManager.ServiceNotFoundException extends java.lang.Exception {
-    ctor @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public SeServiceManager.ServiceNotFoundException(@NonNull String);
+  @FlaggedApi("android.nfc.enable_nfc_mainline") public static class SeServiceManager.ServiceNotFoundException extends java.lang.Exception {
+    ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public SeServiceManager.ServiceNotFoundException(@NonNull String);
   }
 
-  @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public static final class SeServiceManager.ServiceRegisterer {
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Nullable public android.os.IBinder get();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.os.IBinder getOrThrow() throws android.se.omapi.SeServiceManager.ServiceNotFoundException;
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void register(@NonNull android.os.IBinder);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Nullable public android.os.IBinder tryGet();
+  @FlaggedApi("android.nfc.enable_nfc_mainline") public static final class SeServiceManager.ServiceRegisterer {
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public android.os.IBinder get();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.os.IBinder getOrThrow() throws android.se.omapi.SeServiceManager.ServiceNotFoundException;
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void register(@NonNull android.os.IBinder);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public android.os.IBinder tryGet();
   }
 
 }
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 8b3696a..57e2e73 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -23,6 +23,11 @@
     method @Deprecated public android.app.Notification.Builder setTimeout(long);
   }
 
+  public static final class Notification.WearableExtender implements android.app.Notification.Extender {
+    method @Deprecated public android.graphics.Bitmap getBackground();
+    method @Deprecated public android.app.Notification.WearableExtender setBackground(android.graphics.Bitmap);
+  }
+
 }
 
 package android.app.slice {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 220482d..1d88e00 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -540,7 +540,7 @@
     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 @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 @FlaggedApi("android.app.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);
@@ -3193,7 +3193,7 @@
 
   public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
     method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
-    method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void addActivityPolicyExemption(@NonNull android.content.ComponentName);
+    method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void addActivityPolicyExemption(@NonNull android.content.ComponentName);
     method public void addSoundEffectListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
     method @NonNull public android.content.Context createContext();
@@ -3214,9 +3214,9 @@
     method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
     method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
-    method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName);
+    method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName);
     method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
-    method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int);
+    method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
   }
@@ -3232,7 +3232,7 @@
     method public int getDefaultActivityPolicy();
     method public int getDefaultNavigationPolicy();
     method public int getDevicePolicy(int);
-    method @FlaggedApi(Flags.FLAG_VDM_CUSTOM_HOME) @Nullable public android.content.ComponentName getHomeComponent();
+    method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @Nullable public android.content.ComponentName getHomeComponent();
     method public int getLockState();
     method @Nullable public String getName();
     method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
@@ -3247,7 +3247,7 @@
     field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
     field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
     field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
-    field @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3
+    field @FlaggedApi("android.companion.virtual.flags.dynamic_policy") public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3
     field public static final int POLICY_TYPE_AUDIO = 1; // 0x1
     field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
     field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
@@ -3264,7 +3264,7 @@
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
-    method @FlaggedApi(Flags.FLAG_VDM_CUSTOM_HOME) @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
+    method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
@@ -3805,8 +3805,8 @@
   public class PackageInstaller {
     method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
     method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
-    method @FlaggedApi(Flags.FLAG_ARCHIVING) @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @FlaggedApi(Flags.FLAG_ARCHIVING) @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
     field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
     field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
@@ -3817,8 +3817,8 @@
     field public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE";
     field public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
     field public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH";
-    field @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
-    field @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
+    field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
+    field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
     field public static final int LOCATION_DATA_APP = 0; // 0x0
     field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
     field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
@@ -3879,7 +3879,7 @@
     method public static void forceSafeLabels();
     method @Deprecated @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager);
     method @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager, @FloatRange(from=0) float, int);
-    field @FlaggedApi(Flags.FLAG_ARCHIVING) public boolean isArchived;
+    field @FlaggedApi("android.content.pm.archiving") public boolean isArchived;
   }
 
   public abstract class PackageManager {
@@ -3929,7 +3929,7 @@
     method @RequiresPermission(android.Manifest.permission.SET_HARMFUL_APP_WARNINGS) public void setHarmfulAppWarning(@NonNull String, @Nullable CharSequence);
     method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable String);
     method @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo);
-    method @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED) @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo, int);
+    method @FlaggedApi("android.content.pm.quarantined_enabled") @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo, int);
     method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setSyntheticAppDetailsActivityEnabled(@NonNull String, boolean);
     method public void setSystemAppState(@NonNull String, int);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String, boolean);
@@ -3980,7 +3980,7 @@
     field public static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 512; // 0x200
     field public static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED = 256; // 0x100
     field public static final int FLAG_PERMISSION_USER_SET = 1; // 0x1
-    field @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED) public static final int FLAG_SUSPEND_QUARANTINED = 1; // 0x1
+    field @FlaggedApi("android.content.pm.quarantined_enabled") public static final int FLAG_SUSPEND_QUARANTINED = 1; // 0x1
     field public static final int INSTALL_FAILED_ALREADY_EXISTS = -1; // 0xffffffff
     field public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13; // 0xfffffff3
     field public static final int INSTALL_FAILED_CONTAINER_ERROR = -18; // 0xffffffee
@@ -9632,67 +9632,67 @@
 
 package android.nfc.cardemulation {
 
-  @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public final class AidGroup implements android.os.Parcelable {
-    ctor @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public AidGroup(@NonNull java.util.List<java.lang.String>, @Nullable String);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Nullable public static android.nfc.cardemulation.AidGroup createFromXml(@NonNull org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int describeContents();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dump(@NonNull android.util.proto.ProtoOutputStream);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public java.util.List<java.lang.String> getAids();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getCategory();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void writeAsXml(@NonNull org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.AidGroup> CREATOR;
+  @FlaggedApi("android.nfc.enable_nfc_mainline") public final class AidGroup implements android.os.Parcelable {
+    ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public AidGroup(@NonNull java.util.List<java.lang.String>, @Nullable String);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public static android.nfc.cardemulation.AidGroup createFromXml(@NonNull org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.util.proto.ProtoOutputStream);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getAids();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getCategory();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeAsXml(@NonNull org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.AidGroup> CREATOR;
   }
 
-  @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public final class ApduServiceInfo implements android.os.Parcelable {
-    ctor @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int describeContents();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public java.util.List<android.nfc.cardemulation.AidGroup> getAidGroups();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public java.util.List<java.lang.String> getAids();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getCategoryForAid(@NonNull String);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.content.ComponentName getComponent();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getDescription();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.nfc.cardemulation.AidGroup getDynamicAidGroupForCategory(@NonNull String);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Nullable public String getOffHostSecureElement();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public java.util.List<java.lang.String> getPrefixAids();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getSettingsActivityName();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public java.util.List<java.lang.String> getSubsetAids();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int getUid();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public boolean hasCategory(@NonNull String);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public boolean isOnHost();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public CharSequence loadAppLabel(@NonNull android.content.pm.PackageManager);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.graphics.drawable.Drawable loadBanner(@NonNull android.content.pm.PackageManager);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public boolean removeDynamicAidGroupForCategory(@NonNull String);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public boolean requiresScreenOn();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public boolean requiresUnlock();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void resetOffHostSecureElement();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void setDynamicAidGroup(@NonNull android.nfc.cardemulation.AidGroup);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void setOffHostSecureElement(@NonNull String);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.ApduServiceInfo> CREATOR;
+  @FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable {
+    ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.AidGroup> getAidGroups();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getAids();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getCategoryForAid(@NonNull String);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.content.ComponentName getComponent();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getDescription();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.nfc.cardemulation.AidGroup getDynamicAidGroupForCategory(@NonNull String);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public String getOffHostSecureElement();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getPrefixAids();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSettingsActivityName();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getSubsetAids();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getUid();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean hasCategory(@NonNull String);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean isOnHost();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadAppLabel(@NonNull android.content.pm.PackageManager);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadBanner(@NonNull android.content.pm.PackageManager);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public boolean removeDynamicAidGroupForCategory(@NonNull String);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresScreenOn();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresUnlock();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void resetOffHostSecureElement();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setDynamicAidGroup(@NonNull android.nfc.cardemulation.AidGroup);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setOffHostSecureElement(@NonNull String);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.ApduServiceInfo> CREATOR;
   }
 
-  @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public final class NfcFServiceInfo implements android.os.Parcelable {
-    ctor @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public NfcFServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int describeContents();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.content.ComponentName getComponent();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getDescription();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getNfcid2();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getSystemCode();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getT3tPmm();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int getUid();
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void setDynamicNfcid2(@NonNull String);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void setDynamicSystemCode(@NonNull String);
-    method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.NfcFServiceInfo> CREATOR;
+  @FlaggedApi("android.nfc.enable_nfc_mainline") public final class NfcFServiceInfo implements android.os.Parcelable {
+    ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public NfcFServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.content.ComponentName getComponent();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getDescription();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getNfcid2();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSystemCode();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getT3tPmm();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getUid();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setDynamicNfcid2(@NonNull String);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setDynamicSystemCode(@NonNull String);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.NfcFServiceInfo> CREATOR;
   }
 
 }
@@ -10705,13 +10705,13 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void onGetUnusedAppCount(@NonNull java.util.function.IntConsumer);
     method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable);
     method @Deprecated @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String);
-    method @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int);
     method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
     method @Deprecated @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable);
-    method @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable);
     method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
@@ -16777,13 +16777,13 @@
     field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; // 0x3
     field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2
     field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff
-    field @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT = 8; // 0x8
-    field @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT = 8; // 0x8
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7
     field public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3
     field public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2
     field public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0
     field public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1
-    field @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; // 0x6
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; // 0x6
     field public static final int SATELLITE_MODEM_STATE_OFF = 4; // 0x4
     field public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5
     field public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 25c48e6..183783b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -62,7 +62,7 @@
 import android.app.servertransaction.ActivityRelaunchItem;
 import android.app.servertransaction.ActivityResultItem;
 import android.app.servertransaction.ClientTransaction;
-import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.DestroyActivityItem;
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.PendingTransactionActions;
 import android.app.servertransaction.PendingTransactionActions.StopInfo;
@@ -375,8 +375,8 @@
     @GuardedBy("mPendingOverrideConfigs")
     private final ArrayMap<IBinder, Configuration> mPendingOverrideConfigs = new ArrayMap<>();
     /** The activities to be truly destroyed (not include relaunch). */
-    final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed =
-            Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>());
+    final Map<IBinder, DestroyActivityItem> mActivitiesToBeDestroyed =
+            Collections.synchronizedMap(new ArrayMap<>());
     // List of new activities that should be reported when next we idle.
     final ArrayList<ActivityClientRecord> mNewActivities = new ArrayList<>();
     // Number of activities that are currently visible on-screen.
@@ -5799,7 +5799,7 @@
     }
 
     @Override
-    public Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed() {
+    public Map<IBinder, DestroyActivityItem> getActivitiesToBeDestroyed() {
         return mActivitiesToBeDestroyed;
     }
 
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 98020ff..25075e9 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -19,7 +19,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.servertransaction.ClientTransaction;
-import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.DestroyActivityItem;
 import android.app.servertransaction.PendingTransactionActions;
 import android.app.servertransaction.TransactionExecutor;
 import android.content.Context;
@@ -108,7 +108,7 @@
     // and deliver callbacks.
 
     /** Get activity and its corresponding transaction item which are going to destroy. */
-    public abstract Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed();
+    public abstract Map<IBinder, DestroyActivityItem> getActivitiesToBeDestroyed();
 
     /** Destroy the activity. */
     public abstract void handleDestroyActivity(@NonNull ActivityClientRecord r, boolean finishing,
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 2c42df3..93c2b5a 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -44,6 +44,9 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.admin.DevicePolicyManager;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
@@ -296,6 +299,15 @@
     public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
 
     /**
+     * The call to WearableExtender#setBackground(Bitmap) will have no effect and the passed
+     * Bitmap will not be retained in memory.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    @VisibleForTesting
+    static final long WEARABLE_EXTENDER_BACKGROUND_BLOCKED = 270551184L;
+
+    /**
      * A timestamp related to this notification, in milliseconds since the epoch.
      *
      * Default value: {@link System#currentTimeMillis() Now}.
@@ -11148,9 +11160,20 @@
                 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
                         new Notification[mPages.size()]));
             }
+
             if (mBackground != null) {
-                wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
+                // Keeping WearableExtender backgrounds in memory despite them being deprecated has
+                // added noticeable increase in system server and system ui memory usage. After
+                // target VERSION_CODE#VANILLA_ICE_CREAM the background will not be populated
+                // anymore.
+                if (CompatChanges.isChangeEnabled(WEARABLE_EXTENDER_BACKGROUND_BLOCKED)) {
+                    Log.d(TAG, "Use of background in WearableExtenders has been deprecated and "
+                            + "will not be populated anymore.");
+                } else {
+                    wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
+                }
             }
+
             if (mContentIcon != 0) {
                 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
             }
@@ -11369,12 +11392,21 @@
          *
          * @param background the background bitmap
          * @return this object for method chaining
-         * @see android.app.Notification.WearableExtender#getBackground
-         * @deprecated Background images are no longer supported.
+         * @removed Not functional since {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}.
+         *          The wearable background is not used by wearables anymore and uses up
+         *          unnecessary memory.
          */
         @Deprecated
         public WearableExtender setBackground(Bitmap background) {
-            mBackground = background;
+            // Keeping WearableExtender backgrounds in memory despite them being deprecated has
+            // added noticeable increase in system server and system ui memory usage. After
+            // target VERSION_CODE#VANILLA_ICE_CREAM the background will not be populated anymore.
+            if (CompatChanges.isChangeEnabled(WEARABLE_EXTENDER_BACKGROUND_BLOCKED)) {
+                Log.d(TAG, "Use of background in WearableExtenders has been deprecated and "
+                        + "will not be populated anymore.");
+            } else {
+                mBackground = background;
+            }
             return this;
         }
 
@@ -11384,11 +11416,13 @@
          * will work with any notification style.
          *
          * @return the background image
-         * @see android.app.Notification.WearableExtender#setBackground
-         * @deprecated Background images are no longer supported.
+         * @removed Not functional since {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}. The
+         *          wearable background is not used by wearables anymore and uses up
+         *          unnecessary memory.
          */
         @Deprecated
         public Bitmap getBackground() {
+            Log.w(TAG, "Use of background in WearableExtender has been removed, returning null.");
             return mBackground;
         }
 
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index a5b0f18..8617386 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -23,7 +23,6 @@
 import android.app.ClientTransactionHandler;
 import android.app.IApplicationThread;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
@@ -83,23 +82,6 @@
         return mActivityCallbacks;
     }
 
-    /** Get the target activity. */
-    @Nullable
-    @UnsupportedAppUsage
-    public IBinder getActivityToken() {
-        // TODO(b/260873529): remove after we allow multiple activity items in one transaction.
-        if (mLifecycleStateRequest != null) {
-            return mLifecycleStateRequest.getActivityToken();
-        }
-        for (int i = mActivityCallbacks.size() - 1; i >= 0; i--) {
-            final IBinder token = mActivityCallbacks.get(i).getActivityToken();
-            if (token != null) {
-                return token;
-            }
-        }
-        return null;
-    }
-
     /** Get the target state lifecycle request. */
     @VisibleForTesting(visibility = PACKAGE)
     @UnsupportedAppUsage
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index ddb6df1..f9cf075 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -50,6 +50,13 @@
     }
 
     @Override
+    public void postExecute(@NonNull ClientTransactionHandler client,
+            @NonNull PendingTransactionActions pendingActions) {
+        // Cleanup after execution.
+        client.getActivitiesToBeDestroyed().remove(getActivityToken());
+    }
+
+    @Override
     public int getTargetState() {
         return ON_DESTROY;
     }
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 4433673..066f9fe 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -47,7 +47,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.List;
-import java.util.Map;
 
 /**
  * Class that manages transaction execution in the correct order.
@@ -75,34 +74,14 @@
      * either remain in the initial state, or last state needed by a callback.
      */
     public void execute(@NonNull ClientTransaction transaction) {
-        if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Start resolving transaction");
-
-        final IBinder token = transaction.getActivityToken();
-        if (token != null) {
-            final Map<IBinder, ClientTransactionItem> activitiesToBeDestroyed =
-                    mTransactionHandler.getActivitiesToBeDestroyed();
-            final ClientTransactionItem destroyItem = activitiesToBeDestroyed.get(token);
-            if (destroyItem != null) {
-                if (transaction.getLifecycleStateRequest() == destroyItem) {
-                    // It is going to execute the transaction that will destroy activity with the
-                    // token, so the corresponding to-be-destroyed record can be removed.
-                    activitiesToBeDestroyed.remove(token);
-                }
-                if (mTransactionHandler.getActivityClient(token) == null) {
-                    // The activity has not been created but has been requested to destroy, so all
-                    // transactions for the token are just like being cancelled.
-                    Slog.w(TAG, tId(transaction) + "Skip pre-destroyed transaction:\n"
-                            + transactionToString(transaction, mTransactionHandler));
-                    return;
-                }
-            }
+        if (DEBUG_RESOLVER) {
+            Slog.d(TAG, tId(transaction) + "Start resolving transaction");
+            Slog.d(TAG, transactionToString(transaction, mTransactionHandler));
         }
 
-        if (DEBUG_RESOLVER) Slog.d(TAG, transactionToString(transaction, mTransactionHandler));
-
         executeCallbacks(transaction);
-
         executeLifecycleState(transaction);
+
         mPendingActions.clear();
         if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction");
     }
@@ -135,6 +114,14 @@
             final IBinder token = item.getActivityToken();
             ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
 
+            if (token != null && r == null
+                    && mTransactionHandler.getActivitiesToBeDestroyed().containsKey(token)) {
+                // The activity has not been created but has been requested to destroy, so all
+                // transactions for the token are just like being cancelled.
+                Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item);
+                continue;
+            }
+
             if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
             final int postExecutionState = item.getPostExecutionState();
 
@@ -211,6 +198,10 @@
         }
 
         if (r == null) {
+            if (mTransactionHandler.getActivitiesToBeDestroyed().get(token) == lifecycleItem) {
+                // Always cleanup for destroy item.
+                lifecycleItem.postExecute(mTransactionHandler, mPendingActions);
+            }
             // Ignore requests for non-existent client records for now.
             return;
         }
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index 4692f92..ce883cd 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -44,6 +44,7 @@
     private final int mId;
     private final @Nullable String mPersistentId;
     private final @Nullable String mName;
+    private final @Nullable CharSequence mDisplayName;
 
     /**
      * Creates a new instance of {@link VirtualDevice}.
@@ -53,6 +54,18 @@
      */
     public VirtualDevice(@NonNull IVirtualDevice virtualDevice, int id,
             @Nullable String persistentId, @Nullable String name) {
+        this(virtualDevice, id, persistentId, name, null);
+    }
+
+    /**
+     * Creates a new instance of {@link VirtualDevice}. Only to be used by the
+     * VirtualDeviceManagerService.
+     *
+     * @hide
+     */
+    public VirtualDevice(@NonNull IVirtualDevice virtualDevice, int id,
+            @Nullable String persistentId, @Nullable String name,
+            @Nullable CharSequence displayName) {
         if (id <= Context.DEVICE_ID_DEFAULT) {
             throw new IllegalArgumentException("VirtualDevice ID must be greater than "
                     + Context.DEVICE_ID_DEFAULT);
@@ -61,6 +74,7 @@
         mId = id;
         mPersistentId = persistentId;
         mName = name;
+        mDisplayName = displayName;
     }
 
     private VirtualDevice(@NonNull Parcel parcel) {
@@ -68,6 +82,7 @@
         mId = parcel.readInt();
         mPersistentId = parcel.readString8();
         mName = parcel.readString8();
+        mDisplayName = parcel.readCharSequence();
     }
 
     /**
@@ -112,6 +127,15 @@
     }
 
     /**
+     * Returns the human-readable name of the virtual device, if defined, which is suitable to be
+     * shown in UI.
+     */
+    @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
+    public @Nullable CharSequence getDisplayName() {
+        return mDisplayName;
+    }
+
+    /**
      * Returns the IDs of all virtual displays that belong to this device, if any.
      *
      * <p>The actual {@link android.view.Display} objects can be obtained by passing the returned
@@ -156,6 +180,7 @@
         dest.writeInt(mId);
         dest.writeString8(mPersistentId);
         dest.writeString8(mName);
+        dest.writeCharSequence(mDisplayName);
     }
 
     @Override
@@ -165,6 +190,7 @@
                 + " mId=" + mId
                 + " mPersistentId=" + mPersistentId
                 + " mName=" + mName
+                + " mDisplayName=" + mDisplayName
                 + ")";
     }
 
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 3e96c96..d0e13cd 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -34,3 +34,10 @@
     description: "Enable Virtual Camera"
     bug: "270352264"
 }
+
+flag {
+  name: "stream_permissions"
+  namespace: "virtual_devices"
+  description: "Enable streaming permission dialogs to Virtual Devices"
+  bug: "291737919"
+}
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 727716e..12442ba 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -147,8 +147,8 @@
      *
      * <p>Consists of (from the LSB):
      * <li>
-     *     <ul>132bit: Station ID number.
-     *     <ul>14bit: HD_SUBCHANNEL.
+     *     <ul>32bit: Station ID number.
+     *     <ul>4bit: HD_SUBCHANNEL.
      *     <ul>18bit: AMFM_FREQUENCY.
      * </li>
      *
diff --git a/core/java/android/net/metrics/WakeupEvent.java b/core/java/android/net/metrics/WakeupEvent.java
index af9a73c..53a3ea5 100644
--- a/core/java/android/net/metrics/WakeupEvent.java
+++ b/core/java/android/net/metrics/WakeupEvent.java
@@ -29,7 +29,7 @@
     public String iface;
     public int uid;
     public int ethertype;
-    public MacAddress dstHwAddr;
+    public MacAddress dstHwAddr;  // actually used to store a src mac address
     public String srcIp;
     public String dstIp;
     public int ipNextHeader;
@@ -44,7 +44,7 @@
         j.add(iface);
         j.add("uid: " + Integer.toString(uid));
         j.add("eth=0x" + Integer.toHexString(ethertype));
-        j.add("dstHw=" + dstHwAddr);
+        j.add("srcMac=" + dstHwAddr);  // really!! http://b/292404319#comment11
         if (ipNextHeader > 0) {
             j.add("ipNxtHdr=" + ipNextHeader);
             j.add("srcIp=" + srcIp);
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 77be5d4..1294f98 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -6,3 +6,10 @@
   description: "enable device aware permission APIs"
   bug: "274852670"
 }
+
+flag {
+  name: "voice_activation_permission_apis"
+  namespace: "permissions"
+  description: "enable voice activation permission APIs"
+  bug: "287264308"
+}
\ No newline at end of file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e829ca2..f40232b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9908,6 +9908,48 @@
          */
         public static final String DOCK_SETUP_STATE = "dock_setup_state";
 
+
+        /**
+         * Default, indicates that the user has not yet started the hub mode tutorial.
+         *
+         * @hide
+         */
+        public static final int HUB_MODE_TUTORIAL_NOT_STARTED = 0;
+
+        /**
+         * Indicates that the user has started but not yet completed the hub mode tutorial.
+         * One of the possible states for {@link #HUB_MODE_TUTORIAL_STATE}.
+         *
+         * @hide
+         */
+        public static final int HUB_MODE_TUTORIAL_STARTED = 1;
+
+        /**
+         * Indicates that the user has completed the hub mode tutorial.
+         * One of the possible states for {@link #HUB_MODE_TUTORIAL_STATE}.
+         *
+         * @hide
+         */
+        public static final int HUB_MODE_TUTORIAL_COMPLETED = 10;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({
+                HUB_MODE_TUTORIAL_NOT_STARTED,
+                HUB_MODE_TUTORIAL_STARTED,
+                HUB_MODE_TUTORIAL_COMPLETED
+        })
+        public @interface HubModeTutorialState {
+        }
+
+        /**
+         * Defines the user's current state of navigating through the hub mode tutorial.
+         * The possible states are defined in {@link HubModeTutorialState}.
+         *
+         * @hide
+         */
+        public static final String HUB_MODE_TUTORIAL_STATE = "hub_mode_tutorial_state";
+
         /**
          * The default NFC payment component
          * @hide
@@ -11105,6 +11147,12 @@
         public static final String BLUETOOTH_ON_WHILE_DRIVING = "bluetooth_on_while_driving";
 
         /**
+         * Volume dialog timeout in ms.
+         * @hide
+         */
+        public static final String VOLUME_DIALOG_DISMISS_TIMEOUT = "volume_dialog_dismiss_timeout";
+
+        /**
          * What behavior should be invoked when the volume hush gesture is triggered
          * One of VOLUME_HUSH_OFF, VOLUME_HUSH_VIBRATE, VOLUME_HUSH_MUTE.
          *
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 94d8516..6a82f6d 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -959,8 +959,8 @@
                 mKeyphraseMetadata = new KeyphraseMetadata(1, mText, fakeSupportedLocales,
                         AlwaysOnHotwordDetector.RECOGNITION_MODE_VOICE_TRIGGER);
             }
-            notifyStateChangedLocked();
         }
+        notifyStateChanged(availability);
     }
 
     /**
@@ -1370,8 +1370,8 @@
 
             mAvailability = STATE_INVALID;
             mIsAvailabilityOverriddenByTestApi = false;
-            notifyStateChangedLocked();
         }
+        notifyStateChanged(STATE_INVALID);
         super.destroy();
     }
 
@@ -1401,6 +1401,8 @@
      */
     // TODO(b/281608561): remove the enrollment flow from AlwaysOnHotwordDetector
     void onSoundModelsChanged() {
+        boolean notifyError = false;
+
         synchronized (mLock) {
             if (mAvailability == STATE_INVALID
                     || mAvailability == STATE_HARDWARE_UNAVAILABLE
@@ -1441,6 +1443,9 @@
                     // calling stopRecognition where there is no started session.
                     Log.w(TAG, "Failed to stop recognition after enrollment update: code="
                             + result);
+
+                    // Execute a refresh availability task - which should then notify of a change.
+                    new RefreshAvailabilityTask().execute();
                 } catch (Exception e) {
                     Slog.w(TAG, "Failed to stop recognition after enrollment update", e);
                     if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
@@ -1449,14 +1454,14 @@
                                         + Log.getStackTraceString(e),
                                 FailureSuggestedAction.RECREATE_DETECTOR));
                     } else {
-                        updateAndNotifyStateChangedLocked(STATE_ERROR);
+                        notifyError = true;
                     }
-                    return;
                 }
             }
+        }
 
-            // Execute a refresh availability task - which should then notify of a change.
-            new RefreshAvailabilityTask().execute();
+        if (notifyError) {
+            updateAndNotifyStateChanged(STATE_ERROR);
         }
     }
 
@@ -1572,10 +1577,11 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void updateAndNotifyStateChangedLocked(int availability) {
-        updateAvailabilityLocked(availability);
-        notifyStateChangedLocked();
+    private void updateAndNotifyStateChanged(int availability) {
+        synchronized (mLock) {
+            updateAvailabilityLocked(availability);
+        }
+        notifyStateChanged(availability);
     }
 
     @GuardedBy("mLock")
@@ -1589,17 +1595,17 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void notifyStateChangedLocked() {
+    private void notifyStateChanged(int newAvailability) {
         Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED);
-        message.arg1 = mAvailability;
+        message.arg1 = newAvailability;
         message.sendToTarget();
     }
 
-    @GuardedBy("mLock")
     private void sendUnknownFailure(String failureMessage) {
-        // update but do not call onAvailabilityChanged callback for STATE_ERROR
-        updateAvailabilityLocked(STATE_ERROR);
+        synchronized (mLock) {
+            // update but do not call onAvailabilityChanged callback for STATE_ERROR
+            updateAvailabilityLocked(STATE_ERROR);
+        }
         Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE, failureMessage).sendToTarget();
     }
 
@@ -1802,19 +1808,17 @@
                             availability = STATE_KEYPHRASE_UNENROLLED;
                         }
                     }
-                    updateAndNotifyStateChangedLocked(availability);
                 }
+                updateAndNotifyStateChanged(availability);
             } catch (Exception e) {
                 // Any exception here not caught will crash the process because AsyncTask does not
                 // bubble up the exceptions to the client app, so we must propagate it to the app.
                 Slog.w(TAG, "Failed to refresh availability", e);
-                synchronized (mLock) {
-                    if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
-                        sendUnknownFailure(
-                                "Failed to refresh availability: " + Log.getStackTraceString(e));
-                    } else {
-                        updateAndNotifyStateChangedLocked(STATE_ERROR);
-                    }
+                if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
+                    sendUnknownFailure(
+                            "Failed to refresh availability: " + Log.getStackTraceString(e));
+                } else {
+                    updateAndNotifyStateChanged(STATE_ERROR);
                 }
             }
 
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index bdd0a9c..e4e8b7b5b 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -30,8 +31,10 @@
 import android.graphics.TextureLayer;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.flags.Flags;
 
 /**
  * <p>A TextureView can be used to display a content stream, such as that
@@ -51,9 +54,9 @@
  *       <th style="text-align: center;">SurfaceView</th>
  *     </tr>
  *     <tr>
- *       <td>Supports alpha</td>
+ *       <td>Supports View alpha</td>
  *       <td style="text-align: center;">X</td>
- *       <td style="text-align: center;">&nbsp;</td>
+ *       <td style="text-align: center;">U+</td>
  *     </tr>
  *     <tr>
  *       <td>Supports rotations</td>
@@ -194,6 +197,9 @@
     private Canvas mCanvas;
     private int mSaveCount;
 
+    @FloatRange(from = 0.0) float mFrameRate;
+    @Surface.FrameRateCompatibility int mFrameRateCompatibility;
+
     private final Object[] mNativeWindowLock = new Object[0];
     // Set by native code, do not write!
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -465,6 +471,16 @@
             mLayer.setSurfaceTexture(mSurface);
             mSurface.setDefaultBufferSize(getWidth(), getHeight());
             mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
+            if (Flags.toolkitSetFrameRate()) {
+                mSurface.setOnSetFrameRateListener(
+                        (surfaceTexture, frameRate, compatibility, strategy) -> {
+                            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                                Trace.instant(Trace.TRACE_TAG_VIEW, "setFrameRate: " + frameRate);
+                            }
+                            mFrameRate = frameRate;
+                            mFrameRateCompatibility = compatibility;
+                        }, mAttachInfo.mHandler);
+            }
 
             if (mListener != null && createNewSurface) {
                 mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight());
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 55374b9..5eaded2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8562,6 +8562,15 @@
      * announcements every time a View is updated.
      *
      * <p>
+     * For notifying users about errors, such as in a login screen with text that displays an
+     * "incorrect password" notification, that view should send an AccessibilityEvent of type
+     * {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR} and set
+     * {@link AccessibilityNodeInfo#setError(CharSequence)} instead. Custom widgets should expose
+     * error-setting methods that support accessibility automatically. For example, instead of
+     * explicitly sending this event when using a TextView, use
+     * {@link android.widget.TextView#setError(CharSequence)}.
+     *
+     * <p>
      * Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the
      * user interface. While a live region may send different types of events generated by the view,
      * state description will send {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events of
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index a88e394..451acbe 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -122,7 +122,7 @@
             TYPE_PARAMETER_PROCESS_RUNNING,
             TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT,
             TYPE_PARAMETER_ACTIVITY_CREATED,
-            TYPE_PARAMETER_ALLOW_ICON,
+            TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN,
             TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN,
             TYPE_PARAMETER_WINDOWLESS,
             TYPE_PARAMETER_LEGACY_SPLASH_SCREEN
@@ -143,7 +143,7 @@
     /** @hide */
     public static final int TYPE_PARAMETER_ACTIVITY_CREATED = 0x00000010;
     /** @hide */
-    public static final int TYPE_PARAMETER_ALLOW_ICON = 0x00000020;
+    public static final int TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN = 0x00000020;
     /**
      * The parameter which indicates if the activity has finished drawing.
      * @hide
diff --git a/core/java/com/android/internal/net/OWNERS b/core/java/com/android/internal/net/OWNERS
index 71f997b..7157683 100644
--- a/core/java/com/android/internal/net/OWNERS
+++ b/core/java/com/android/internal/net/OWNERS
@@ -1,4 +1,4 @@
 set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
 
 jsharkey@android.com
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index c6f5086..7eeac29 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -154,6 +154,7 @@
 
     void addQsTile(in ComponentName tile);
     void remQsTile(in ComponentName tile);
+    void setQsTiles(in String[] tiles);
     void clickQsTile(in ComponentName tile);
     void handleSystemKey(in KeyEvent key);
 
diff --git a/core/java/com/android/server/net/OWNERS b/core/java/com/android/server/net/OWNERS
index 62c5737..c24680e9 100644
--- a/core/java/com/android/server/net/OWNERS
+++ b/core/java/com/android/server/net/OWNERS
@@ -1,2 +1,2 @@
 set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 9ed4155..3795fc8 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -102,6 +102,7 @@
     static_libs: [
         "libnativehelper_lazy",
         "libziparchive_for_incfs",
+        "libguiflags",
     ],
 
     export_include_dirs: [
diff --git a/core/jni/android_graphics_SurfaceTexture.cpp b/core/jni/android_graphics_SurfaceTexture.cpp
index 21487ab..50832a5 100644
--- a/core/jni/android_graphics_SurfaceTexture.cpp
+++ b/core/jni/android_graphics_SurfaceTexture.cpp
@@ -17,27 +17,24 @@
 #undef LOG_TAG
 #define LOG_TAG "SurfaceTexture"
 
-#include <stdio.h>
-
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
-
+#include <com_android_graphics_libgui_flags.h>
+#include <cutils/atomic.h>
 #include <gui/BufferQueue.h>
 #include <gui/Surface.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <stdio.h>
 #include <surfacetexture/SurfaceTexture.h>
 #include <surfacetexture/surface_texture_platform.h>
-
-#include "core_jni_helpers.h"
-
-#include <cutils/atomic.h>
 #include <utils/Log.h>
 #include <utils/misc.h>
 
+#include "core_jni_helpers.h"
 #include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedLocalRef.h>
 
 // ----------------------------------------------------------------------------
 
@@ -55,6 +52,7 @@
     jfieldID  producer;
     jfieldID  frameAvailableListener;
     jmethodID postEvent;
+    jmethodID postOnSetFrameRateEvent;
 };
 static fields_t fields;
 
@@ -139,61 +137,81 @@
 
 // ----------------------------------------------------------------------------
 
-class JNISurfaceTextureContext : public SurfaceTexture::FrameAvailableListener
-{
+class JNISurfaceTextureContextCommon {
 public:
-    JNISurfaceTextureContext(JNIEnv* env, jobject weakThiz, jclass clazz);
-    virtual ~JNISurfaceTextureContext();
-    virtual void onFrameAvailable(const BufferItem& item);
+    JNISurfaceTextureContextCommon(JNIEnv* env, jobject weakThiz, jclass clazz)
+          : mWeakThiz(env->NewGlobalRef(weakThiz)), mClazz((jclass)env->NewGlobalRef(clazz)) {}
 
-private:
-    static JNIEnv* getJNIEnv();
+    virtual ~JNISurfaceTextureContextCommon() {
+        JNIEnv* env = getJNIEnv();
+        if (env != NULL) {
+            env->DeleteGlobalRef(mWeakThiz);
+            env->DeleteGlobalRef(mClazz);
+        } else {
+            ALOGW("leaking JNI object references");
+        }
+    }
+
+    void onFrameAvailable(const BufferItem& item) {
+        JNIEnv* env = getJNIEnv();
+        if (env != NULL) {
+            env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz);
+        } else {
+            ALOGW("onFrameAvailable event will not posted");
+        }
+    }
+
+protected:
+    static JNIEnv* getJNIEnv() {
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+        if (env == NULL) {
+            JavaVMAttachArgs args = {JNI_VERSION_1_4, "JNISurfaceTextureContext", NULL};
+            JavaVM* vm = AndroidRuntime::getJavaVM();
+            int result = vm->AttachCurrentThreadAsDaemon(&env, (void*)&args);
+            if (result != JNI_OK) {
+                ALOGE("thread attach failed: %#x", result);
+                return NULL;
+            }
+        }
+        return env;
+    }
 
     jobject mWeakThiz;
     jclass mClazz;
 };
 
-JNISurfaceTextureContext::JNISurfaceTextureContext(JNIEnv* env,
-        jobject weakThiz, jclass clazz) :
-    mWeakThiz(env->NewGlobalRef(weakThiz)),
-    mClazz((jclass)env->NewGlobalRef(clazz))
-{}
+class JNISurfaceTextureContextFrameAvailableListener
+      : public JNISurfaceTextureContextCommon,
+        public SurfaceTexture::FrameAvailableListener {
+public:
+    JNISurfaceTextureContextFrameAvailableListener(JNIEnv* env, jobject weakThiz, jclass clazz)
+          : JNISurfaceTextureContextCommon(env, weakThiz, clazz) {}
+    void onFrameAvailable(const BufferItem& item) override {
+        JNISurfaceTextureContextCommon::onFrameAvailable(item);
+    }
+};
 
-JNIEnv* JNISurfaceTextureContext::getJNIEnv() {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-    if (env == NULL) {
-        JavaVMAttachArgs args = {
-            JNI_VERSION_1_4, "JNISurfaceTextureContext", NULL };
-        JavaVM* vm = AndroidRuntime::getJavaVM();
-        int result = vm->AttachCurrentThreadAsDaemon(&env, (void*)&args);
-        if (result != JNI_OK) {
-            ALOGE("thread attach failed: %#x", result);
-            return NULL;
+class JNISurfaceTextureContextListener : public JNISurfaceTextureContextCommon,
+                                         public SurfaceTexture::SurfaceTextureListener {
+public:
+    JNISurfaceTextureContextListener(JNIEnv* env, jobject weakThiz, jclass clazz)
+          : JNISurfaceTextureContextCommon(env, weakThiz, clazz) {}
+
+    void onFrameAvailable(const BufferItem& item) override {
+        JNISurfaceTextureContextCommon::onFrameAvailable(item);
+    }
+
+    void onSetFrameRate(float frameRate, int8_t compatibility,
+                        int8_t changeFrameRateStrategy) override {
+        JNIEnv* env = getJNIEnv();
+        if (env != NULL) {
+            env->CallStaticVoidMethod(mClazz, fields.postOnSetFrameRateEvent, mWeakThiz, frameRate,
+                                      compatibility, changeFrameRateStrategy);
+        } else {
+            ALOGW("onSetFrameRate event will not posted");
         }
     }
-    return env;
-}
-
-JNISurfaceTextureContext::~JNISurfaceTextureContext()
-{
-    JNIEnv* env = getJNIEnv();
-    if (env != NULL) {
-        env->DeleteGlobalRef(mWeakThiz);
-        env->DeleteGlobalRef(mClazz);
-    } else {
-        ALOGW("leaking JNI object references");
-    }
-}
-
-void JNISurfaceTextureContext::onFrameAvailable(const BufferItem& /* item */)
-{
-    JNIEnv* env = getJNIEnv();
-    if (env != NULL) {
-        env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz);
-    } else {
-        ALOGW("onFrameAvailable event will not posted");
-    }
-}
+};
 
 // ----------------------------------------------------------------------------
 
@@ -229,6 +247,13 @@
     if (fields.postEvent == NULL) {
         ALOGE("can't find android/graphics/SurfaceTexture.postEventFromNative");
     }
+
+    fields.postOnSetFrameRateEvent =
+            env->GetStaticMethodID(clazz, "postOnSetFrameRateEventFromNative",
+                                   "(Ljava/lang/ref/WeakReference;FII)V");
+    if (fields.postOnSetFrameRateEvent == NULL) {
+        ALOGE("can't find android/graphics/SurfaceTexture.postOnSetFrameRateEventFromNative");
+    }
 }
 
 static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,
@@ -274,17 +299,27 @@
         return;
     }
 
-    sp<JNISurfaceTextureContext> ctx(new JNISurfaceTextureContext(env, weakThiz,
-            clazz));
-    surfaceTexture->setFrameAvailableListener(ctx);
-    SurfaceTexture_setFrameAvailableListener(env, thiz, ctx);
+    if (com::android::graphics::libgui::flags::bq_setframerate()) {
+        sp<JNISurfaceTextureContextListener> ctx(
+                new JNISurfaceTextureContextListener(env, weakThiz, clazz));
+        surfaceTexture->setSurfaceTextureListener(ctx);
+    } else {
+        sp<JNISurfaceTextureContextFrameAvailableListener> ctx(
+                new JNISurfaceTextureContextFrameAvailableListener(env, weakThiz, clazz));
+        surfaceTexture->setFrameAvailableListener(ctx);
+        SurfaceTexture_setFrameAvailableListener(env, thiz, ctx);
+    }
 }
 
 static void SurfaceTexture_finalize(JNIEnv* env, jobject thiz)
 {
     sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));
-    surfaceTexture->setFrameAvailableListener(0);
-    SurfaceTexture_setFrameAvailableListener(env, thiz, 0);
+    if (com::android::graphics::libgui::flags::bq_setframerate()) {
+        surfaceTexture->setSurfaceTextureListener(0);
+    } else {
+        surfaceTexture->setFrameAvailableListener(0);
+        SurfaceTexture_setFrameAvailableListener(env, thiz, 0);
+    }
     SurfaceTexture_setSurfaceTexture(env, thiz, 0);
     SurfaceTexture_setProducer(env, thiz, 0);
 }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c00a776f..4e073ca 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4830,7 +4830,7 @@
     <string translatable="false" name="config_deviceSpecificInputMethodManagerService"></string>
 
     <!-- Component name of media projection permission dialog -->
-    <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
+    <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.mediaprojection.permission.MediaProjectionPermissionActivity</string>
 
     <!-- Corner radius of system dialogs -->
     <dimen name="config_dialogCornerRadius">28dp</dimen>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 48dc167..7f3e014 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -69,6 +69,7 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
+import android.compat.testing.PlatformCompatChangeRule;
 import android.content.Context;
 import android.content.Intent;
 import android.content.LocusId;
@@ -95,16 +96,20 @@
 import android.widget.RemoteViews;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.R;
 import com.android.internal.util.ContrastColorUtil;
 
 import junit.framework.Assert;
 
+import libcore.junit.util.compat.CoreCompatChangeRule;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
 import java.util.List;
@@ -116,6 +121,9 @@
 
     private Context mContext;
 
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getContext();
@@ -1777,6 +1785,42 @@
         assertThat(recoveredExtender.getColor()).isEqualTo(1234);
     }
 
+    @Test
+    @CoreCompatChangeRule.EnableCompatChanges({Notification.WEARABLE_EXTENDER_BACKGROUND_BLOCKED})
+    public void wearableBackgroundBlockEnabled_wearableBackgroundSet_valueRemainsNull() {
+        Notification.WearableExtender extender = new Notification.WearableExtender();
+        Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
+        extender.setBackground(bitmap);
+        Notification notif =
+                new Notification.Builder(mContext, "test id")
+                        .setSmallIcon(1)
+                        .setContentTitle("test_title")
+                        .extend(extender)
+                        .build();
+
+        Notification.WearableExtender result = new Notification.WearableExtender(notif);
+        Assert.assertNull(result.getBackground());
+    }
+
+    @Test
+    @CoreCompatChangeRule.DisableCompatChanges({Notification.WEARABLE_EXTENDER_BACKGROUND_BLOCKED})
+    public void wearableBackgroundBlockDisabled_wearableBackgroundSet_valueKeepsBitmap() {
+        Notification.WearableExtender extender = new Notification.WearableExtender();
+        Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
+        extender.setBackground(bitmap);
+        Notification notif =
+                new Notification.Builder(mContext, "test id")
+                        .setSmallIcon(1)
+                        .setContentTitle("test_title")
+                        .extend(extender)
+                        .build();
+
+        Notification.WearableExtender result = new Notification.WearableExtender(notif);
+        Bitmap resultBitmap = result.getBackground();
+        assertNotNull(resultBitmap);
+        Assert.assertEquals(bitmap, resultBitmap);
+    }
+
     private void assertValid(Notification.Colors c) {
         // Assert that all colors are populated
         assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
diff --git a/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java b/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java
new file mode 100644
index 0000000..ecd75a8
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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 android.app.servertransaction;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityThread.ActivityClientRecord;
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link DestroyActivityItem}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksCoreTests:DestroyActivityItemTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class DestroyActivityItemTest {
+
+    @Mock
+    private ClientTransactionHandler mHandler;
+    @Mock
+    private PendingTransactionActions mPendingActions;
+    @Mock
+    private IBinder mActivityToken;
+
+    // Can't mock final class.
+    private ActivityClientRecord mActivityClientRecord;
+
+    private ArrayMap<IBinder, DestroyActivityItem> mActivitiesToBeDestroyed;
+    private DestroyActivityItem mItem;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mItem = DestroyActivityItem.obtain(
+                mActivityToken, false /* finished */, 123 /* configChanges */);
+        mActivityClientRecord = new ActivityClientRecord();
+        mActivitiesToBeDestroyed = new ArrayMap<>();
+
+        doReturn(mActivitiesToBeDestroyed).when(mHandler).getActivitiesToBeDestroyed();
+    }
+
+    @Test
+    public void testPreExecute() {
+        mItem.preExecute(mHandler);
+
+        assertEquals(1, mActivitiesToBeDestroyed.size());
+        assertEquals(mItem, mActivitiesToBeDestroyed.get(mActivityToken));
+    }
+
+    @Test
+    public void testPostExecute() {
+        mItem.preExecute(mHandler);
+        mItem.postExecute(mHandler, mPendingActions);
+
+        assertTrue(mActivitiesToBeDestroyed.isEmpty());
+    }
+
+    @Test
+    public void testExecute() {
+        mItem.execute(mHandler, mActivityClientRecord, mPendingActions);
+
+        verify(mHandler).handleDestroyActivity(eq(mActivityClientRecord), eq(false) /* finishing */,
+                eq(123) /* configChanges */, eq(false) /* getNonConfigInstance */, any());
+    }
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index a1a2bdb..44a4d58 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -28,6 +28,7 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.inOrder;
@@ -247,7 +248,7 @@
 
     @Test
     public void testDoNotLaunchDestroyedActivity() {
-        final Map<IBinder, ClientTransactionItem> activitiesToBeDestroyed = new ArrayMap<>();
+        final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
         when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
         // Assume launch transaction is still in queue, so there is no client record.
         when(mTransactionHandler.getActivityClient(any())).thenReturn(null);
@@ -259,7 +260,7 @@
                 DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
         destroyTransaction.preExecute(mTransactionHandler);
         // The activity should be added to to-be-destroyed container.
-        assertEquals(1, mTransactionHandler.getActivitiesToBeDestroyed().size());
+        assertEquals(1, activitiesToBeDestroyed.size());
 
         // A previous queued launch transaction runs on main thread (execute).
         final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */);
@@ -274,7 +275,7 @@
 
         // After the destroy transaction has been executed, the token should be removed.
         mExecutor.execute(destroyTransaction);
-        assertEquals(0, mTransactionHandler.getActivitiesToBeDestroyed().size());
+        assertTrue(activitiesToBeDestroyed.isEmpty());
     }
 
     @Test
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index dfe5012..dd82fed 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+import android.annotation.FloatRange;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -24,8 +25,10 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Trace;
 import android.view.Surface;
 import android.view.TextureView;
+import android.view.flags.Flags;
 
 import java.lang.ref.WeakReference;
 
@@ -79,6 +82,7 @@
     private final Looper mCreatorLooper;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private Handler mOnFrameAvailableHandler;
+    private Handler mOnSetFrameRateHandler;
 
     /**
      * These fields are used by native code, do not access or modify.
@@ -100,6 +104,21 @@
     }
 
     /**
+     * Callback interface for being notified that a producer set a frame rate
+     * @hide
+     */
+    public interface OnSetFrameRateListener {
+        /**
+         * Called when the producer sets a frame rate
+         * @hide
+         */
+        void onSetFrameRate(SurfaceTexture surfaceTexture,
+                            @FloatRange(from = 0.0) float frameRate,
+                                 @Surface.FrameRateCompatibility int compatibility,
+                                 @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy);
+    }
+
+    /**
      * Exception thrown when a SurfaceTexture couldn't be created or resized.
      *
      * @deprecated No longer thrown. {@link android.view.Surface.OutOfResourcesException}
@@ -224,6 +243,48 @@
         }
     }
 
+    private static class SetFrameRateArgs {
+        SetFrameRateArgs(@FloatRange(from = 0.0) float frameRate,
+                                @Surface.FrameRateCompatibility int compatibility,
+                   @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy) {
+            this.mFrameRate = frameRate;
+            this.mCompatibility = compatibility;
+            this.mChangeFrameRateStrategy = changeFrameRateStrategy;
+        }
+        final float mFrameRate;
+        final int mCompatibility;
+        final int mChangeFrameRateStrategy;
+    }
+
+    /**
+     * Register a callback to be invoked when the producer sets a frame rate using
+     * Surface.setFrameRate.
+     * @hide
+     */
+    public void setOnSetFrameRateListener(@Nullable final OnSetFrameRateListener listener,
+                                            @Nullable Handler handler) {
+        if (listener != null) {
+            Looper looper = handler != null ? handler.getLooper() :
+                    mCreatorLooper != null ? mCreatorLooper : Looper.getMainLooper();
+            mOnSetFrameRateHandler = new Handler(looper, null, true /*async*/) {
+                @Override
+                public void handleMessage(Message msg) {
+                    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onSetFrameRateHandler");
+                    try {
+                        SetFrameRateArgs args = (SetFrameRateArgs) msg.obj;
+                        listener.onSetFrameRate(SurfaceTexture.this,
+                                args.mFrameRate, args.mCompatibility,
+                                args.mChangeFrameRateStrategy);
+                    } finally {
+                        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                    }
+                }
+            };
+        } else {
+            mOnSetFrameRateHandler = null;
+        }
+    }
+
     /**
      * Set the default size of the image buffers.  The image producer may override the buffer size,
      * in which case the producer-set buffer size will be used, not the default size set by this
@@ -418,6 +479,35 @@
     }
 
     /**
+     * This method is invoked from native code only.
+     * @hide
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    private static void postOnSetFrameRateEventFromNative(WeakReference<SurfaceTexture> weakSelf,
+            @FloatRange(from = 0.0) float frameRate,
+            @Surface.FrameRateCompatibility int compatibility,
+            @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "postOnSetFrameRateEventFromNative");
+        try {
+            if (Flags.toolkitSetFrameRate()) {
+                SurfaceTexture st = weakSelf.get();
+                if (st != null) {
+                    Handler handler = st.mOnSetFrameRateHandler;
+                    if (handler != null) {
+                        Message msg = new Message();
+                        msg.obj = new SetFrameRateArgs(frameRate, compatibility,
+                                changeFrameRateStrategy);
+                        handler.sendMessage(msg);
+                    }
+                }
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+
+    }
+
+    /**
      * Returns {@code true} if the SurfaceTexture is single-buffered.
      * @hide
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
index 6ea6516..72fc8686 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
@@ -30,7 +30,7 @@
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
-import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_ICON;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS;
 
 import android.window.StartingWindowInfo;
@@ -52,7 +52,8 @@
         final boolean processRunning = (parameter & TYPE_PARAMETER_PROCESS_RUNNING) != 0;
         final boolean allowTaskSnapshot = (parameter & TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT) != 0;
         final boolean activityCreated = (parameter & TYPE_PARAMETER_ACTIVITY_CREATED) != 0;
-        final boolean allowIcon = (parameter & TYPE_PARAMETER_ALLOW_ICON) != 0;
+        final boolean isSolidColorSplashScreen =
+                (parameter & TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN) != 0;
         final boolean legacySplashScreen =
                 ((parameter & TYPE_PARAMETER_LEGACY_SPLASH_SCREEN) != 0);
         final boolean activityDrawn = (parameter & TYPE_PARAMETER_ACTIVITY_DRAWN) != 0;
@@ -66,13 +67,13 @@
                         + "processRunning=%b, "
                         + "allowTaskSnapshot=%b, "
                         + "activityCreated=%b, "
-                        + "allowIcon=%b, "
+                        + "isSolidColorSplashScreen=%b, "
                         + "legacySplashScreen=%b, "
                         + "activityDrawn=%b, "
                         + "windowless=%b, "
                         + "topIsHome=%b",
                 newTask, taskSwitch, processRunning, allowTaskSnapshot, activityCreated,
-                allowIcon, legacySplashScreen, activityDrawn, windowlessSurface,
+                isSolidColorSplashScreen, legacySplashScreen, activityDrawn, windowlessSurface,
                 topIsHome);
 
         if (windowlessSurface) {
@@ -80,7 +81,7 @@
         }
         if (!topIsHome) {
             if (!processRunning || newTask || (taskSwitch && !activityCreated)) {
-                return getSplashscreenType(allowIcon, legacySplashScreen);
+                return getSplashscreenType(isSolidColorSplashScreen, legacySplashScreen);
             }
         }
 
@@ -94,18 +95,18 @@
                 }
             }
             if (!activityDrawn && !topIsHome) {
-                return getSplashscreenType(allowIcon, legacySplashScreen);
+                return getSplashscreenType(isSolidColorSplashScreen, legacySplashScreen);
             }
         }
         return STARTING_WINDOW_TYPE_NONE;
     }
 
-    private static int getSplashscreenType(boolean allowIcon, boolean legacySplashScreen) {
-        if (allowIcon) {
-            return legacySplashScreen ? STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+    private static int getSplashscreenType(boolean solidColorSplashScreen,
+            boolean legacySplashScreen) {
+        return solidColorSplashScreen
+                ? STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN
+                : legacySplashScreen
+                        ? STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
                         : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
-        } else {
-            return STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
index b444bad..b0e847f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
@@ -26,7 +26,7 @@
 
 open class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
index e2ab989..f847e40 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
@@ -26,7 +26,7 @@
 
 open class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
index 22b8102..31e52e2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -27,7 +27,7 @@
 open class DismissSplitScreenByDividerGesturalNavLandscape :
     DismissSplitScreenByDivider(Rotation.ROTATION_90) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
index 3fb014f..0870073 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -27,7 +27,7 @@
 open class DismissSplitScreenByDividerGesturalNavPortrait :
     DismissSplitScreenByDivider(Rotation.ROTATION_0) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
index ea1f942..1bb6c45 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -27,7 +27,7 @@
 open class DismissSplitScreenByGoHomeGesturalNavLandscape :
     DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
index 8f23a79..bb084d6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -27,7 +27,7 @@
 open class DismissSplitScreenByGoHomeGesturalNavPortrait :
     DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
index b0f39e5..bc5857f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
@@ -26,7 +26,7 @@
 
 open class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
index 6ce8746..e4faa4a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
@@ -26,7 +26,7 @@
 
 open class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
index 9f74edf..7431974 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -27,7 +27,7 @@
 open class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
     EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
index 1e4055e..c2b0609 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -27,7 +27,7 @@
 open class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
     EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
index c3b8132..7d1072d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -27,7 +27,7 @@
 open class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
     EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
index 7756d04..79760d1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -27,7 +27,7 @@
 open class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
     EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
index c72aa5a..ff3729a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -27,7 +27,7 @@
 open class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
     EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
index cc88f27..ffbcfe4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -27,7 +27,7 @@
 open class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
     EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
index 87b38b1..5e937c0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -27,7 +27,7 @@
 open class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
     EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
index ca347ed..89db8a0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -27,7 +27,7 @@
 open class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
     EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
index 6597819..bf4ee7a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -27,7 +27,7 @@
 open class EnterSplitScreenFromOverviewGesturalNavLandscape :
     EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
index 6df31fc..7e96f06 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -27,7 +27,7 @@
 open class EnterSplitScreenFromOverviewGesturalNavPortrait :
     EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
index 02596c5..a84deac 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -27,7 +27,7 @@
 open class SwitchAppByDoubleTapDividerGesturalNavLandscape :
     SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
index 9d579f6..afcc743 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -27,7 +27,7 @@
 open class SwitchAppByDoubleTapDividerGesturalNavPortrait :
     SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
index da85342..2f96f5f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -27,7 +27,7 @@
 open class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
     SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
index 1ae2c9e..efbeb76 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -27,7 +27,7 @@
 open class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
     SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
index b1b56257..232fccc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -27,7 +27,7 @@
 open class SwitchBackToSplitFromHomeGesturalNavLandscape :
     SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
index 08c437e..d7f2845 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -27,7 +27,7 @@
 open class SwitchBackToSplitFromHomeGesturalNavPortrait :
     SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index efbf86d..019c946 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -27,7 +27,7 @@
 open class SwitchBackToSplitFromRecentGesturalNavLandscape :
     SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index f7072fa..5678861 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -27,7 +27,7 @@
 open class SwitchBackToSplitFromRecentGesturalNavPortrait :
     SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
index d80d112..b3c4ea2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -27,7 +27,7 @@
 open class SwitchBetweenSplitPairsGesturalNavLandscape :
     SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
index 30ec37a..f88180f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -27,7 +27,7 @@
 open class SwitchBetweenSplitPairsGesturalNavPortrait :
     SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
index 1e086d2..391cb9b7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -28,7 +28,7 @@
 @RunWith(BlockJUnit4ClassRunner::class)
 open class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
index 932f892..4af133d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -28,7 +28,7 @@
 @RunWith(BlockJUnit4ClassRunner::class)
 open class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
     @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false)
+    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
 
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 664fbb2..f2c29f4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -180,7 +180,8 @@
 
         controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
-        val wct = getLatestWct(type = TRANSIT_OPEN, handlerClass = OneShotRemoteHandler::class.java)
+        val wct =
+            getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
         assertThat(wct.hierarchyOps).hasSize(3)
         // Expect order to be from bottom: home, task1, task2
         wct.assertReorderAt(index = 0, homeTask)
@@ -198,7 +199,8 @@
 
         controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
-        val wct = getLatestWct(type = TRANSIT_OPEN, handlerClass = OneShotRemoteHandler::class.java)
+        val wct =
+            getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
         assertThat(wct.hierarchyOps).hasSize(3)
         // Expect order to be from bottom: home, task1, task2
         wct.assertReorderAt(index = 0, homeTask)
@@ -216,7 +218,8 @@
 
         controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
-        val wct = getLatestWct(type = TRANSIT_OPEN, handlerClass = OneShotRemoteHandler::class.java)
+        val wct =
+            getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
         assertThat(wct.hierarchyOps).hasSize(3)
         // Expect order to be from bottom: home, task1, task2
         wct.assertReorderAt(index = 0, homeTask)
@@ -230,7 +233,8 @@
 
         controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
-        val wct = getLatestWct(type = TRANSIT_OPEN, handlerClass = OneShotRemoteHandler::class.java)
+        val wct =
+            getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
         assertThat(wct.hierarchyOps).hasSize(1)
         wct.assertReorderAt(index = 0, homeTask)
     }
@@ -246,7 +250,8 @@
 
         controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
-        val wct = getLatestWct(type = TRANSIT_OPEN, handlerClass = OneShotRemoteHandler::class.java)
+        val wct =
+            getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
         assertThat(wct.hierarchyOps).hasSize(2)
         // Expect order to be from bottom: home, task
         wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 94ed06c..f76ea06 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -17,6 +17,7 @@
 #include "RenderThread.h"
 
 #include <GrContextOptions.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
 #include <android-base/properties.h>
 #include <dlfcn.h>
 #include <gl/GrGLInterface.h>
@@ -286,7 +287,7 @@
     auto glesVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));
     auto size = glesVersion ? strlen(glesVersion) : -1;
     cacheManager().configureContext(&options, glesVersion, size);
-    sk_sp<GrDirectContext> grContext(GrDirectContext::MakeGL(std::move(glInterface), options));
+    sk_sp<GrDirectContext> grContext(GrDirectContexts::MakeGL(std::move(glInterface), options));
     LOG_ALWAYS_FATAL_IF(!grContext.get());
     setGrContext(grContext);
 }
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index b0cdb05..230fb07 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -1167,7 +1167,10 @@
                                 streamType);
                 if (attributes != null) {
                     mUsage = attributes.mUsage;
-                    mContentType = attributes.mContentType;
+                    // on purpose ignoring the content type: stream types are deprecated for
+                    // playback, making assumptions about the content type is prone to
+                    // interpretation errors for ambiguous types such as STREAM_TTS and STREAM_MUSIC
+                    //mContentType = attributes.mContentType;
                     mFlags = attributes.getAllFlags();
                     mMuteHapticChannels = attributes.areHapticChannelsMuted();
                     mIsContentSpatialized = attributes.isContentSpatialized();
@@ -1177,49 +1180,47 @@
                     mSource = attributes.mSource;
                 }
             }
-            if (mContentType == CONTENT_TYPE_UNKNOWN) {
-                switch (streamType) {
-                    case AudioSystem.STREAM_VOICE_CALL:
-                        mContentType = CONTENT_TYPE_SPEECH;
-                        break;
-                    case AudioSystem.STREAM_SYSTEM_ENFORCED:
-                        mFlags |= FLAG_AUDIBILITY_ENFORCED;
-                        // intended fall through, attributes in common with STREAM_SYSTEM
-                    case AudioSystem.STREAM_SYSTEM:
-                        mContentType = CONTENT_TYPE_SONIFICATION;
-                        break;
-                    case AudioSystem.STREAM_RING:
-                        mContentType = CONTENT_TYPE_SONIFICATION;
-                        break;
-                    case AudioSystem.STREAM_MUSIC:
-                        mContentType = CONTENT_TYPE_MUSIC;
-                        break;
-                    case AudioSystem.STREAM_ALARM:
-                        mContentType = CONTENT_TYPE_SONIFICATION;
-                        break;
-                    case AudioSystem.STREAM_NOTIFICATION:
-                        mContentType = CONTENT_TYPE_SONIFICATION;
-                        break;
-                    case AudioSystem.STREAM_BLUETOOTH_SCO:
-                        mContentType = CONTENT_TYPE_SPEECH;
-                        mFlags |= FLAG_SCO;
-                        break;
-                    case AudioSystem.STREAM_DTMF:
-                        mContentType = CONTENT_TYPE_SONIFICATION;
-                        break;
-                    case AudioSystem.STREAM_TTS:
-                        mContentType = CONTENT_TYPE_SONIFICATION;
-                        mFlags |= FLAG_BEACON;
-                        break;
-                    case AudioSystem.STREAM_ACCESSIBILITY:
-                        mContentType = CONTENT_TYPE_SPEECH;
-                        break;
-                    case AudioSystem.STREAM_ASSISTANT:
-                        mContentType = CONTENT_TYPE_SPEECH;
-                        break;
-                    default:
-                        Log.e(TAG, "Invalid stream type " + streamType + " for AudioAttributes");
-                }
+            switch (streamType) {
+                case AudioSystem.STREAM_VOICE_CALL:
+                    mContentType = CONTENT_TYPE_SPEECH;
+                    break;
+                case AudioSystem.STREAM_SYSTEM_ENFORCED:
+                    mFlags |= FLAG_AUDIBILITY_ENFORCED;
+                    // intended fall through, attributes in common with STREAM_SYSTEM
+                case AudioSystem.STREAM_SYSTEM:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    break;
+                case AudioSystem.STREAM_RING:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    break;
+                case AudioSystem.STREAM_ALARM:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    break;
+                case AudioSystem.STREAM_NOTIFICATION:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    break;
+                case AudioSystem.STREAM_BLUETOOTH_SCO:
+                    mContentType = CONTENT_TYPE_SPEECH;
+                    mFlags |= FLAG_SCO;
+                    break;
+                case AudioSystem.STREAM_DTMF:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    break;
+                case AudioSystem.STREAM_TTS:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    mFlags |= FLAG_BEACON;
+                    break;
+                case AudioSystem.STREAM_ACCESSIBILITY:
+                    mContentType = CONTENT_TYPE_SPEECH;
+                    break;
+                case AudioSystem.STREAM_ASSISTANT:
+                    mContentType = CONTENT_TYPE_SPEECH;
+                    break;
+                case AudioSystem.STREAM_MUSIC:
+                    // leaving as CONTENT_TYPE_UNKNOWN
+                    break;
+                default:
+                    Log.e(TAG, "Invalid stream type " + streamType + " for AudioAttributes");
             }
             if (mUsage == USAGE_UNKNOWN) {
                 mUsage = usageForStreamType(streamType);
diff --git a/packages/SettingsLib/tests/integ/AndroidManifest.xml b/packages/SettingsLib/tests/integ/AndroidManifest.xml
index a95da303..32048ca 100644
--- a/packages/SettingsLib/tests/integ/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidManifest.xml
@@ -41,7 +41,7 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.settingslib"
+        android:targetPackage="com.android.settingslib.tests.integ"
         android:label="Tests for SettingsLib">
     </instrumentation>
 </manifest>
diff --git a/packages/SettingsLib/tests/integ/AndroidTest.xml b/packages/SettingsLib/tests/integ/AndroidTest.xml
index b5d0947..d0aee88 100644
--- a/packages/SettingsLib/tests/integ/AndroidTest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidTest.xml
@@ -21,7 +21,7 @@
     <option name="test-suite-tag" value="apct" />
     <option name="test-tag" value="SettingsLibTests" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="com.android.settingslib" />
+        <option name="package" value="com.android.settingslib.tests.integ" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false"/>
     </test>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 1c33544..f6f75de 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -143,6 +143,7 @@
         Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
         Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
         Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
+        Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT,
         Settings.Secure.VOLUME_HUSH_GESTURE,
         Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT,
         Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
@@ -243,6 +244,7 @@
         Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING,
         Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
         Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED,
-        Settings.Secure.SEARCH_LONG_PRESS_HOME_ENABLED
+        Settings.Secure.SEARCH_LONG_PRESS_HOME_ENABLED,
+        Settings.Secure.HUB_MODE_TUTORIAL_STATE
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 301fd6f..8d13f01 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -212,6 +212,7 @@
         VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.VOLUME_HUSH_GESTURE, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(
                 Secure.ENABLED_NOTIFICATION_LISTENERS,
@@ -389,5 +390,6 @@
         VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
                 BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.DND_CONFIGS_MIGRATED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.HUB_MODE_TUTORIAL_STATE, NON_NEGATIVE_INTEGER_VALIDATOR);
     }
 }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b5b873c..9bfc4be 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -634,7 +634,7 @@
 
         <!-- started from MediaProjectionManager -->
         <activity
-            android:name=".media.MediaProjectionPermissionActivity"
+            android:name=".mediaprojection.permission.MediaProjectionPermissionActivity"
             android:exported="true"
             android:theme="@style/Theme.SystemUI.MediaProjectionAlertDialog"
             android:finishOnCloseSystemDialogs="true"
@@ -643,7 +643,7 @@
             android:visibleToInstantApps="true"/>
 
         <activity
-            android:name=".media.MediaProjectionAppSelectorActivity"
+            android:name=".mediaprojection.appselector.MediaProjectionAppSelectorActivity"
             android:theme="@style/Theme.SystemUI.MediaProjectionAppSelector"
             android:finishOnCloseSystemDialogs="true"
             android:excludeFromRecents="true"
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
index 0cc259a..a62c984 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
@@ -151,7 +151,7 @@
                 element.lastAlpha = alpha
             }
         }
-        .testTag(key.name)
+        .testTag(key.testTag)
 }
 
 private fun shouldDrawElement(
@@ -167,7 +167,8 @@
             state.fromScene == state.toScene ||
             !layoutImpl.isTransitionReady(state) ||
             state.fromScene !in element.sceneValues ||
-            state.toScene !in element.sceneValues
+            state.toScene !in element.sceneValues ||
+            !isSharedElementEnabled(layoutImpl, state, element.key)
     ) {
         return true
     }
@@ -191,6 +192,26 @@
     }
 }
 
+private fun isSharedElementEnabled(
+    layoutImpl: SceneTransitionLayoutImpl,
+    transition: TransitionState.Transition,
+    element: ElementKey,
+): Boolean {
+    val spec = layoutImpl.transitions.transitionSpec(transition.fromScene, transition.toScene)
+    val sharedInFromScene = spec.transformations(element, transition.fromScene).shared
+    val sharedInToScene = spec.transformations(element, transition.toScene).shared
+
+    // The sharedElement() transformation must either be null or be the same in both scenes.
+    if (sharedInFromScene != sharedInToScene) {
+        error(
+            "Different sharedElement() transformations matched $element (from=$sharedInFromScene " +
+                "to=$sharedInToScene)"
+        )
+    }
+
+    return sharedInFromScene?.enabled ?: true
+}
+
 /**
  * Chain the [com.android.compose.animation.scene.transformation.ModifierTransformation] applied
  * throughout the current transition, if any.
@@ -213,7 +234,7 @@
 
             return layoutImpl.transitions
                 .transitionSpec(fromScene, state.toScene)
-                .transformations(element.key)
+                .transformations(element.key, scene.key)
                 .modifier
                 .fold(this) { modifier, transformation ->
                     with(transformation) {
@@ -407,17 +428,20 @@
     // The element is shared: interpolate between the value in fromScene and the value in toScene.
     // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared
     // elements follow the finger direction.
-    if (fromValues != null && toValues != null) {
+    val isSharedElement = fromValues != null && toValues != null
+    if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) {
         return lerp(
-            sceneValue(fromValues),
-            sceneValue(toValues),
+            sceneValue(fromValues!!),
+            sceneValue(toValues!!),
             transitionProgress,
         )
     }
 
     val transformation =
         transformation(
-            layoutImpl.transitions.transitionSpec(fromScene, toScene).transformations(element.key)
+            layoutImpl.transitions
+                .transitionSpec(fromScene, toScene)
+                .transformations(element.key, scene.key)
         )
         // If there is no transformation explicitly associated to this element value, let's use
         // the value given by the system (like the current position and size given by the layout
@@ -426,12 +450,21 @@
 
     // Get the transformed value, i.e. the target value at the beginning (for entering elements) or
     // end (for leaving elements) of the transition.
+    val sceneValues =
+        checkNotNull(
+            when {
+                isSharedElement && scene.key == fromScene -> fromValues
+                isSharedElement -> toValues
+                else -> fromValues ?: toValues
+            }
+        )
+
     val targetValue =
         transformation.transform(
             layoutImpl,
             scene,
             element,
-            fromValues ?: toValues!!,
+            sceneValues,
             state,
             idleValue,
         )
@@ -440,7 +473,7 @@
     val rangeProgress = transformation.range?.progress(transitionProgress) ?: transitionProgress
 
     // Interpolate between the value at rest and the value before entering/after leaving.
-    val isEntering = fromValues == null
+    val isEntering = scene.key == toScene
     return if (isEntering) {
         lerp(targetValue, idleValue, rangeProgress)
     } else {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt
new file mode 100644
index 0000000..98dbb67
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.compose.animation.scene
+
+/** An interface to match one or more elements. */
+interface ElementMatcher {
+    /** Whether the element with key [key] in scene [scene] matches this matcher. */
+    fun matches(key: ElementKey, scene: SceneKey): Boolean
+}
+
+/**
+ * Returns an [ElementMatcher] that matches elements in [scene] also matching [this]
+ * [ElementMatcher].
+ */
+fun ElementMatcher.inScene(scene: SceneKey): ElementMatcher {
+    val delegate = this
+    val matcherScene = scene
+    return object : ElementMatcher {
+        override fun matches(key: ElementKey, scene: SceneKey): Boolean {
+            return scene == matcherScene && delegate.matches(key, scene)
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
index f7ebe2f..b7acc48 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
@@ -16,6 +16,8 @@
 
 package com.android.compose.animation.scene
 
+import androidx.annotation.VisibleForTesting
+
 /**
  * A base class to create unique keys, associated to an [identity] that is used to check the
  * equality of two key instances.
@@ -41,6 +43,7 @@
     name: String,
     identity: Any = Object(),
 ) : Key(name, identity) {
+    @VisibleForTesting val testTag: String = "scene:$name"
 
     /** The unique [ElementKey] identifying this scene's root element. */
     val rootElementKey = ElementKey(name, identity)
@@ -61,7 +64,9 @@
      */
     val isBackground: Boolean = false,
 ) : Key(name, identity), ElementMatcher {
-    override fun matches(key: ElementKey): Boolean {
+    @VisibleForTesting val testTag: String = "element:$name"
+
+    override fun matches(key: ElementKey, scene: SceneKey): Boolean {
         return key == this
     }
 
@@ -73,7 +78,9 @@
         /** Matches any element whose [key identity][ElementKey.identity] matches [predicate]. */
         fun withIdentity(predicate: (Any) -> Boolean): ElementMatcher {
             return object : ElementMatcher {
-                override fun matches(key: ElementKey): Boolean = predicate(key.identity)
+                override fun matches(key: ElementKey, scene: SceneKey): Boolean {
+                    return predicate(key.identity)
+                }
             }
         }
     }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
index b44c8ef..3985233 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
@@ -25,6 +25,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.zIndex
 
@@ -45,7 +46,9 @@
 
     @Composable
     fun Content(modifier: Modifier = Modifier) {
-        Box(modifier.zIndex(zIndex).onPlaced { size = it.size }) { scope.content() }
+        Box(modifier.zIndex(zIndex).onPlaced { size = it.size }.testTag(key.testTag)) {
+            scope.content()
+        }
     }
 
     override fun toString(): String {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
index f4e3902..75dcb2e 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -16,6 +16,7 @@
 
 package com.android.compose.animation.scene
 
+import androidx.annotation.VisibleForTesting
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.snap
 import androidx.compose.ui.geometry.Offset
@@ -28,6 +29,7 @@
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
 import com.android.compose.animation.scene.transformation.ScaleSize
+import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.animation.scene.transformation.Transformation
 import com.android.compose.animation.scene.transformation.Translate
 import com.android.compose.ui.util.fastForEach
@@ -35,11 +37,12 @@
 
 /** The transitions configuration of a [SceneTransitionLayout]. */
 class SceneTransitions(
-    private val transitionSpecs: List<TransitionSpec>,
+    @get:VisibleForTesting val transitionSpecs: List<TransitionSpec>,
 ) {
     private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>()
 
-    internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec {
+    @VisibleForTesting
+    fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec {
         return cache.getOrPut(from) { mutableMapOf() }.getOrPut(to) { findSpec(from, to) }
     }
 
@@ -97,7 +100,8 @@
     val transformations: List<Transformation>,
     val spec: AnimationSpec<Float>,
 ) {
-    private val cache = mutableMapOf<ElementKey, ElementTransformations>()
+    // TODO(b/302300957): Make sure this cache does not infinitely grow.
+    private val cache = mutableMapOf<ElementKey, MutableMap<SceneKey, ElementTransformations>>()
 
     internal fun reverse(): TransitionSpec {
         return copy(
@@ -107,12 +111,18 @@
         )
     }
 
-    internal fun transformations(element: ElementKey): ElementTransformations {
-        return cache.getOrPut(element) { computeTransformations(element) }
+    internal fun transformations(element: ElementKey, scene: SceneKey): ElementTransformations {
+        return cache
+            .getOrPut(element) { mutableMapOf() }
+            .getOrPut(scene) { computeTransformations(element, scene) }
     }
 
     /** Filter [transformations] to compute the [ElementTransformations] of [element]. */
-    private fun computeTransformations(element: ElementKey): ElementTransformations {
+    private fun computeTransformations(
+        element: ElementKey,
+        scene: SceneKey,
+    ): ElementTransformations {
+        var shared: SharedElementTransformation? = null
         val modifier = mutableListOf<ModifierTransformation>()
         var offset: PropertyTransformation<Offset>? = null
         var size: PropertyTransformation<IntSize>? = null
@@ -126,16 +136,16 @@
                 is Translate,
                 is EdgeTranslate,
                 is AnchoredTranslate -> {
-                    throwIfNotNull(offset, element, property = "offset")
+                    throwIfNotNull(offset, element, name = "offset")
                     offset = root as PropertyTransformation<Offset>
                 }
                 is ScaleSize,
                 is AnchoredSize -> {
-                    throwIfNotNull(size, element, property = "size")
+                    throwIfNotNull(size, element, name = "size")
                     size = root as PropertyTransformation<IntSize>
                 }
                 is Fade -> {
-                    throwIfNotNull(alpha, element, property = "alpha")
+                    throwIfNotNull(alpha, element, name = "alpha")
                     alpha = root as PropertyTransformation<Float>
                 }
                 is RangedPropertyTransformation -> onPropertyTransformation(root, current.delegate)
@@ -143,32 +153,37 @@
         }
 
         transformations.fastForEach { transformation ->
-            if (!transformation.matcher.matches(element)) {
+            if (!transformation.matcher.matches(element, scene)) {
                 return@fastForEach
             }
 
             when (transformation) {
+                is SharedElementTransformation -> {
+                    throwIfNotNull(shared, element, name = "shared")
+                    shared = transformation
+                }
                 is ModifierTransformation -> modifier.add(transformation)
                 is PropertyTransformation<*> -> onPropertyTransformation(transformation)
             }
         }
 
-        return ElementTransformations(modifier, offset, size, alpha)
+        return ElementTransformations(shared, modifier, offset, size, alpha)
     }
 
     private fun throwIfNotNull(
-        previous: PropertyTransformation<*>?,
+        previous: Transformation?,
         element: ElementKey,
-        property: String,
+        name: String,
     ) {
         if (previous != null) {
-            error("$element has multiple transformations for its $property property")
+            error("$element has multiple $name transformations")
         }
     }
 }
 
 /** The transformations of an element during a transition. */
 internal class ElementTransformations(
+    val shared: SharedElementTransformation?,
     val modifier: List<ModifierTransformation>,
     val offset: PropertyTransformation<Offset>?,
     val size: PropertyTransformation<IntSize>?,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
index fb12b90..4966977 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -116,6 +116,14 @@
     )
 
     /**
+     * Configure the shared transition when [matcher] is shared between two scenes.
+     *
+     * @param enabled whether the matched element(s) should actually be shared in this transition.
+     *   Defaults to true.
+     */
+    fun sharedElement(matcher: ElementMatcher, enabled: Boolean = true)
+
+    /**
      * Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and
      * using the given [shape].
      *
@@ -127,6 +135,13 @@
      * the result.
      */
     fun punchHole(matcher: ElementMatcher, bounds: ElementKey, shape: Shape = RectangleShape)
+
+    /**
+     * Adds the transformations in [builder] but in reversed order. This allows you to partially
+     * reuse the definition of the transition from scene `Foo` to scene `Bar` inside the definition
+     * of the transition from scene `Bar` to scene `Foo`.
+     */
+    fun reversed(builder: TransitionBuilder.() -> Unit)
 }
 
 @TransitionDsl
@@ -179,12 +194,6 @@
     fun anchoredSize(matcher: ElementMatcher, anchor: ElementKey)
 }
 
-/** An interface to match one or more elements. */
-interface ElementMatcher {
-    /** Whether the element with key [key] matches this matcher. */
-    fun matches(key: ElementKey): Boolean
-}
-
 /** The edge of a [SceneTransitionLayout]. */
 enum class Edge {
     Left,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 48d5638e8b..f1c2717 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -31,6 +31,7 @@
 import com.android.compose.animation.scene.transformation.PunchHole
 import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
 import com.android.compose.animation.scene.transformation.ScaleSize
+import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.animation.scene.transformation.Transformation
 import com.android.compose.animation.scene.transformation.TransformationRange
 import com.android.compose.animation.scene.transformation.Translate
@@ -80,6 +81,7 @@
     override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
 
     private var range: TransformationRange? = null
+    private var reversed = false
     private val durationMillis: Int by lazy {
         val spec = spec
         if (spec !is DurationBasedAnimationSpec) {
@@ -93,6 +95,12 @@
         transformations.add(PunchHole(matcher, bounds, shape))
     }
 
+    override fun reversed(builder: TransitionBuilder.() -> Unit) {
+        reversed = true
+        builder()
+        reversed = false
+    }
+
     override fun fractionRange(
         start: Float?,
         end: Float?,
@@ -103,6 +111,10 @@
         range = null
     }
 
+    override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) {
+        transformations.add(SharedElementTransformation(matcher, enabled))
+    }
+
     override fun timestampRange(
         startMillis: Int?,
         endMillis: Int?,
@@ -122,11 +134,20 @@
     }
 
     private fun transformation(transformation: PropertyTransformation<*>) {
-        if (range != null) {
-            transformations.add(RangedPropertyTransformation(transformation, range!!))
-        } else {
-            transformations.add(transformation)
-        }
+        val transformation =
+            if (range != null) {
+                RangedPropertyTransformation(transformation, range!!)
+            } else {
+                transformation
+            }
+
+        transformations.add(
+            if (reversed) {
+                transformation.reverse()
+            } else {
+                transformation
+            }
+        )
     }
 
     override fun fade(matcher: ElementMatcher) {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
index ce6749d..a650254 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -30,6 +30,14 @@
      */
     val matcher: ElementMatcher
 
+    /**
+     * The range during which the transformation is applied. If it is `null`, then the
+     * transformation will be applied throughout the whole scene transition.
+     */
+    // TODO(b/240432457): Move this back to PropertyTransformation.
+    val range: TransformationRange?
+        get() = null
+
     /*
      * Reverse this transformation. This is called when we use Transition(from = A, to = B) when
      * animating from B to A and there is no Transition(from = B, to = A) defined.
@@ -37,6 +45,11 @@
     fun reverse(): Transformation = this
 }
 
+internal class SharedElementTransformation(
+    override val matcher: ElementMatcher,
+    internal val enabled: Boolean,
+) : Transformation
+
 /** A transformation that is applied on the element during the whole transition. */
 internal interface ModifierTransformation : Transformation {
     /** Apply the transformation to [element]. */
@@ -53,13 +66,6 @@
 /** A transformation that changes the value of an element property, like its size or offset. */
 internal sealed interface PropertyTransformation<T> : Transformation {
     /**
-     * The range during which the transformation is applied. If it is `null`, then the
-     * transformation will be applied throughout the whole scene transition.
-     */
-    val range: TransformationRange?
-        get() = null
-
-    /**
      * Transform [value], i.e. the value of the transformed property without this transformation.
      */
     // TODO(b/290184746): Figure out a public API for custom transformations that don't have access
@@ -92,8 +98,7 @@
 }
 
 /** The progress-based range of a [PropertyTransformation]. */
-data class TransformationRange
-private constructor(
+data class TransformationRange(
     val start: Float,
     val end: Float,
 ) {
@@ -133,6 +138,6 @@
     }
 
     companion object {
-        private const val BoundUnspecified = Float.MIN_VALUE
+        const val BoundUnspecified = Float.MIN_VALUE
     }
 }
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 8bd6545..328866e 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -224,7 +224,7 @@
 
         // In scene A, the shared element SharedFoo() is at the top end of the layout and has a size
         // of 50.dp.
-        var sharedFoo = rule.onNodeWithTag(TestElements.Foo.name, useUnmergedTree = true)
+        var sharedFoo = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
         sharedFoo.assertWidthIsEqualTo(50.dp)
         sharedFoo.assertHeightIsEqualTo(50.dp)
         sharedFoo.assertPositionInRootIsEqualTo(
@@ -250,7 +250,7 @@
 
         // We need to use onAllNodesWithTag().onFirst() here given that shared elements are
         // composed and laid out in both scenes (but drawn only in one).
-        sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.name).onFirst()
+        sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.testTag).onFirst()
 
         // In scene B, foo is at the top start (x = 0, y = 0) of the layout and has a size of
         // 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we
@@ -284,7 +284,7 @@
         val expectedLeft = 0.dp
         val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress
 
-        sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.name).onFirst()
+        sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.testTag).onFirst()
         assertThat((layoutState.transitionState as TransitionState.Transition).progress)
             .isEqualTo(interpolatedProgress)
         sharedFoo.assertWidthIsEqualTo(expectedSize)
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
index 275149a0..268057f 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
@@ -23,7 +23,11 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.SemanticsNodeInteractionCollection
+import androidx.compose.ui.test.hasParent
+import androidx.compose.ui.test.hasTestTag
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.onAllNodesWithTag
 import androidx.compose.ui.test.onNodeWithTag
 
 @DslMarker annotation class TransitionTestDsl
@@ -59,8 +63,21 @@
 
 @TransitionTestDsl
 interface TransitionTestAssertionScope {
-    /** Assert on [element]. */
-    fun onElement(element: ElementKey): SemanticsNodeInteraction
+    /**
+     * Assert on [element].
+     *
+     * Note that presence/value assertions on the returned [SemanticsNodeInteraction] will fail if 0
+     * or more than 1 elements matched [element]. If you need to assert on a shared element that
+     * will be present multiple times in the layout during transitions, either specify the [scene]
+     * in which you are matching or use [onSharedElement] instead.
+     */
+    fun onElement(element: ElementKey, scene: SceneKey? = null): SemanticsNodeInteraction
+
+    /**
+     * Assert on a shared [element]. This will throw if [element] is not shared and present only in
+     * one scene during a transition.
+     */
+    fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection
 }
 
 /**
@@ -73,20 +90,22 @@
     toSceneContent: @Composable SceneScope.() -> Unit,
     transition: TransitionBuilder.() -> Unit,
     layoutModifier: Modifier = Modifier,
+    fromScene: SceneKey = TestScenes.SceneA,
+    toScene: SceneKey = TestScenes.SceneB,
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     testTransition(
-        from = TestScenes.SceneA,
-        to = TestScenes.SceneB,
+        from = fromScene,
+        to = toScene,
         transitionLayout = { currentScene, onChangeScene ->
             SceneTransitionLayout(
                 currentScene,
                 onChangeScene,
-                transitions { from(TestScenes.SceneA, to = TestScenes.SceneB, transition) },
+                transitions { from(fromScene, to = toScene, transition) },
                 layoutModifier.fillMaxSize(),
             ) {
-                scene(TestScenes.SceneA, content = fromSceneContent)
-                scene(TestScenes.SceneB, content = toSceneContent)
+                scene(fromScene, content = fromSceneContent)
+                scene(toScene, content = toSceneContent)
             }
         },
         builder,
@@ -111,8 +130,24 @@
     val test = transitionTest(builder)
     val assertionScope =
         object : TransitionTestAssertionScope {
-            override fun onElement(element: ElementKey): SemanticsNodeInteraction {
-                return this@testTransition.onNodeWithTag(element.name)
+            override fun onElement(
+                element: ElementKey,
+                scene: SceneKey?
+            ): SemanticsNodeInteraction {
+                return if (scene == null) {
+                    onNodeWithTag(element.testTag)
+                } else {
+                    onNode(hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag)))
+                }
+            }
+
+            override fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection {
+                val interaction = onAllNodesWithTag(element.testTag)
+                val matches = interaction.fetchSemanticsNodes(atLeastOneRootRequired = false).size
+                if (matches < 2) {
+                    error("Element $element is not shared ($matches matches)")
+                }
+                return interaction
             }
         }
 
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
new file mode 100644
index 0000000..fa94b250
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -0,0 +1,190 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.animation.core.TweenSpec
+import androidx.compose.animation.core.tween
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.transformation.Transformation
+import com.android.compose.animation.scene.transformation.TransformationRange
+import com.google.common.truth.Correspondence
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TransitionDslTest {
+    @Test
+    fun emptyTransitions() {
+        val transitions = transitions {}
+        assertThat(transitions.transitionSpecs).isEmpty()
+    }
+
+    @Test
+    fun manyTransitions() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB)
+            from(TestScenes.SceneB, to = TestScenes.SceneC)
+            from(TestScenes.SceneC, to = TestScenes.SceneA)
+        }
+        assertThat(transitions.transitionSpecs).hasSize(3)
+    }
+
+    @Test
+    fun toFromBuilders() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB)
+            from(TestScenes.SceneB)
+            to(TestScenes.SceneC)
+        }
+
+        assertThat(transitions.transitionSpecs)
+            .comparingElementsUsing(
+                Correspondence.transforming<TransitionSpec, Pair<SceneKey?, SceneKey?>>(
+                    { it?.from to it?.to },
+                    "has (from, to) equal to"
+                )
+            )
+            .containsExactly(
+                TestScenes.SceneA to TestScenes.SceneB,
+                TestScenes.SceneB to null,
+                null to TestScenes.SceneC,
+            )
+    }
+
+    @Test
+    fun defaultTransitionSpec() {
+        val transitions = transitions { from(TestScenes.SceneA, to = TestScenes.SceneB) }
+        val transition = transitions.transitionSpecs.single()
+        assertThat(transition.spec).isInstanceOf(SpringSpec::class.java)
+    }
+
+    @Test
+    fun customTransitionSpec() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB) { spec = tween(durationMillis = 42) }
+        }
+        val transition = transitions.transitionSpecs.single()
+        assertThat(transition.spec).isInstanceOf(TweenSpec::class.java)
+        assertThat((transition.spec as TweenSpec).durationMillis).isEqualTo(42)
+    }
+
+    @Test
+    fun defaultRange() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
+        }
+
+        val transition = transitions.transitionSpecs.single()
+        assertThat(transition.transformations.size).isEqualTo(1)
+        assertThat(transition.transformations.single().range).isEqualTo(null)
+    }
+
+    @Test
+    fun fractionRange() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB) {
+                fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) }
+                fractionRange(start = 0.2f) { fade(TestElements.Foo) }
+                fractionRange(end = 0.9f) { fade(TestElements.Foo) }
+            }
+        }
+
+        val transition = transitions.transitionSpecs.single()
+        assertThat(transition.transformations)
+            .comparingElementsUsing(TRANSFORMATION_RANGE)
+            .containsExactly(
+                TransformationRange(start = 0.1f, end = 0.8f),
+                TransformationRange(start = 0.2f, end = TransformationRange.BoundUnspecified),
+                TransformationRange(start = TransformationRange.BoundUnspecified, end = 0.9f),
+            )
+    }
+
+    @Test
+    fun timestampRange() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB) {
+                spec = tween(500)
+
+                timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) }
+                timestampRange(startMillis = 200) { fade(TestElements.Foo) }
+                timestampRange(endMillis = 400) { fade(TestElements.Foo) }
+            }
+        }
+
+        val transition = transitions.transitionSpecs.single()
+        assertThat(transition.transformations)
+            .comparingElementsUsing(TRANSFORMATION_RANGE)
+            .containsExactly(
+                TransformationRange(start = 100 / 500f, end = 300 / 500f),
+                TransformationRange(start = 200 / 500f, end = TransformationRange.BoundUnspecified),
+                TransformationRange(start = TransformationRange.BoundUnspecified, end = 400 / 500f),
+            )
+    }
+
+    @Test
+    fun reversed() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB) {
+                spec = tween(500)
+                reversed {
+                    fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) }
+                    timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) }
+                }
+            }
+        }
+
+        val transition = transitions.transitionSpecs.single()
+        assertThat(transition.transformations)
+            .comparingElementsUsing(TRANSFORMATION_RANGE)
+            .containsExactly(
+                TransformationRange(start = 1f - 0.8f, end = 1f - 0.1f),
+                TransformationRange(start = 1f - 300 / 500f, end = 1f - 100 / 500f),
+            )
+    }
+
+    @Test
+    fun defaultReversed() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB) {
+                spec = tween(500)
+                fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) }
+                timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) }
+            }
+        }
+
+        // Fetch the transition from B to A, which will automatically reverse the transition from A
+        // to B we defined.
+        val transition =
+            transitions.transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA)
+        assertThat(transition.transformations)
+            .comparingElementsUsing(TRANSFORMATION_RANGE)
+            .containsExactly(
+                TransformationRange(start = 1f - 0.8f, end = 1f - 0.1f),
+                TransformationRange(start = 1f - 300 / 500f, end = 1f - 100 / 500f),
+            )
+    }
+
+    companion object {
+        private val TRANSFORMATION_RANGE =
+            Correspondence.transforming<Transformation, TransformationRange?>(
+                { it?.range },
+                "has range equal to"
+            )
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
new file mode 100644
index 0000000..2af3638
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.compose.animation.scene.transformation
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.TestScenes
+import com.android.compose.animation.scene.inScene
+import com.android.compose.animation.scene.testTransition
+import com.android.compose.modifiers.size
+import com.android.compose.test.assertSizeIsEqualTo
+import com.android.compose.test.onEach
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SharedElementTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun testSharedElement() {
+        rule.testTransition(
+            fromSceneContent = {
+                // Foo is at (10, 50) with a size of (20, 80).
+                Box(Modifier.offset(10.dp, 50.dp).element(TestElements.Foo).size(20.dp, 80.dp))
+            },
+            toSceneContent = {
+                // Foo is at (50, 70) with a size of (10, 40).
+                Box(Modifier.offset(50.dp, 70.dp).element(TestElements.Foo).size(10.dp, 40.dp))
+            },
+            transition = {
+                spec = tween(16 * 4, easing = LinearEasing)
+                // Elements should be shared by default.
+            }
+        ) {
+            before {
+                onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp)
+                onElement(TestElements.Foo).assertSizeIsEqualTo(20.dp, 80.dp)
+            }
+            at(0) {
+                onSharedElement(TestElements.Foo).onEach {
+                    assertPositionInRootIsEqualTo(10.dp, 50.dp)
+                    assertSizeIsEqualTo(20.dp, 80.dp)
+                }
+            }
+            at(16) {
+                onSharedElement(TestElements.Foo).onEach {
+                    assertPositionInRootIsEqualTo(20.dp, 55.dp)
+                    assertSizeIsEqualTo(17.5.dp, 70.dp)
+                }
+            }
+            at(32) {
+                onSharedElement(TestElements.Foo).onEach {
+                    assertPositionInRootIsEqualTo(30.dp, 60.dp)
+                    assertSizeIsEqualTo(15.dp, 60.dp)
+                }
+            }
+            at(48) {
+                onSharedElement(TestElements.Foo).onEach {
+                    assertPositionInRootIsEqualTo(40.dp, 65.dp)
+                    assertSizeIsEqualTo(12.5.dp, 50.dp)
+                }
+            }
+            after {
+                onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 70.dp)
+                onElement(TestElements.Foo).assertSizeIsEqualTo(10.dp, 40.dp)
+            }
+        }
+    }
+
+    @Test
+    fun testSharedElementDisabled() {
+        rule.testTransition(
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+            // The full layout is 100x100.
+            layoutModifier = Modifier.size(100.dp),
+            fromSceneContent = {
+                Box(Modifier.fillMaxSize()) {
+                    // Foo is at (10, 50).
+                    Box(Modifier.offset(10.dp, 50.dp).element(TestElements.Foo))
+                }
+            },
+            toSceneContent = {
+                Box(Modifier.fillMaxSize()) {
+                    // Foo is at (50, 60).
+                    Box(Modifier.offset(50.dp, 60.dp).element(TestElements.Foo))
+                }
+            },
+            transition = {
+                spec = tween(16 * 4, easing = LinearEasing)
+
+                // Disable the shared element animation.
+                sharedElement(TestElements.Foo, enabled = false)
+
+                // In SceneA, Foo leaves to the left edge.
+                translate(TestElements.Foo.inScene(TestScenes.SceneA), Edge.Left)
+
+                // In SceneB, Foo comes from the bottom edge.
+                translate(TestElements.Foo.inScene(TestScenes.SceneB), Edge.Bottom)
+            },
+        ) {
+            before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
+            at(0) {
+                onElement(TestElements.Foo, scene = TestScenes.SceneA)
+                    .assertPositionInRootIsEqualTo(10.dp, 50.dp)
+                onElement(TestElements.Foo, scene = TestScenes.SceneB)
+                    .assertPositionInRootIsEqualTo(50.dp, 100.dp)
+            }
+            at(16) {
+                onElement(TestElements.Foo, scene = TestScenes.SceneA)
+                    .assertPositionInRootIsEqualTo(7.5.dp, 50.dp)
+                onElement(TestElements.Foo, scene = TestScenes.SceneB)
+                    .assertPositionInRootIsEqualTo(50.dp, 90.dp)
+            }
+            at(32) {
+                onElement(TestElements.Foo, scene = TestScenes.SceneA)
+                    .assertPositionInRootIsEqualTo(5.dp, 50.dp)
+                onElement(TestElements.Foo, scene = TestScenes.SceneB)
+                    .assertPositionInRootIsEqualTo(50.dp, 80.dp)
+            }
+            at(48) {
+                onElement(TestElements.Foo, scene = TestScenes.SceneA)
+                    .assertPositionInRootIsEqualTo(2.5.dp, 50.dp)
+                onElement(TestElements.Foo, scene = TestScenes.SceneB)
+                    .assertPositionInRootIsEqualTo(50.dp, 70.dp)
+            }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 60.dp) }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt
new file mode 100644
index 0000000..d6f64bf
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.compose.test
+
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.SemanticsNodeInteractionCollection
+
+/** Assert [assert] on each element of [this] [SemanticsNodeInteractionCollection]. */
+fun SemanticsNodeInteractionCollection.onEach(assert: SemanticsNodeInteraction.() -> Unit) {
+    for (i in 0 until this.fetchSemanticsNodes().size) {
+        get(i).assert()
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index c41dc53..cb76ad7 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -507,9 +507,10 @@
         }
     }
 
-    private var isVerifying = AtomicBoolean(false)
+    private var isQueued = AtomicBoolean(false)
     fun verifyLoadedProviders() {
-        val shouldSchedule = isVerifying.compareAndSet(false, true)
+        Log.i(TAG, Thread.currentThread().getStackTrace().toString())
+        val shouldSchedule = isQueued.compareAndSet(false, true)
         if (!shouldSchedule) {
             logger.tryLog(
                 TAG,
@@ -521,48 +522,54 @@
         }
 
         scope.launch(bgDispatcher) {
-            if (keepAllLoaded) {
+            // TODO(b/267372164): Use better threading approach when converting to flows
+            synchronized(availableClocks) {
+                isQueued.set(false)
+                if (keepAllLoaded) {
+                    logger.tryLog(
+                        TAG,
+                        LogLevel.INFO,
+                        {},
+                        { "verifyLoadedProviders: keepAllLoaded=true" }
+                    )
+                    // Enforce that all plugins are loaded if requested
+                    for ((_, info) in availableClocks) {
+                        info.manager?.loadPlugin()
+                    }
+                    return@launch
+                }
+
+                val currentClock = availableClocks[currentClockId]
+                if (currentClock == null) {
+                    logger.tryLog(
+                        TAG,
+                        LogLevel.INFO,
+                        {},
+                        { "verifyLoadedProviders: currentClock=null" }
+                    )
+                    // Current Clock missing, load no plugins and use default
+                    for ((_, info) in availableClocks) {
+                        info.manager?.unloadPlugin()
+                    }
+                    return@launch
+                }
+
                 logger.tryLog(
                     TAG,
                     LogLevel.INFO,
                     {},
-                    { "verifyLoadedProviders: keepAllLoaded=true" }
+                    { "verifyLoadedProviders: load currentClock" }
                 )
-                // Enforce that all plugins are loaded if requested
+                val currentManager = currentClock.manager
+                currentManager?.loadPlugin()
+
                 for ((_, info) in availableClocks) {
-                    info.manager?.loadPlugin()
-                }
-                isVerifying.set(false)
-                return@launch
-            }
-
-            val currentClock = availableClocks[currentClockId]
-            if (currentClock == null) {
-                logger.tryLog(
-                    TAG,
-                    LogLevel.INFO,
-                    {},
-                    { "verifyLoadedProviders: currentClock=null" }
-                )
-                // Current Clock missing, load no plugins and use default
-                for ((_, info) in availableClocks) {
-                    info.manager?.unloadPlugin()
-                }
-                isVerifying.set(false)
-                return@launch
-            }
-
-            logger.tryLog(TAG, LogLevel.INFO, {}, { "verifyLoadedProviders: load currentClock" })
-            val currentManager = currentClock.manager
-            currentManager?.loadPlugin()
-
-            for ((_, info) in availableClocks) {
-                val manager = info.manager
-                if (manager != null && currentManager != manager) {
-                    manager.unloadPlugin()
+                    val manager = info.manager
+                    if (manager != null && currentManager != manager) {
+                        manager.unloadPlugin()
+                    }
                 }
             }
-            isVerifying.set(false)
         }
     }
 
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 21696fe..6cdd15e 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -233,6 +233,11 @@
     <item type="id" name="pin_pad"/>
 
     <!--
+    Tag used to store pending intent registration listeners in NotificationTemplateViewWrapper
+    -->
+    <item type="id" name="pending_intent_listener_tag" />
+
+    <!--
     Used to tag views programmatically added to the smartspace area so they can be more easily
     removed later.
     -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
index 96a974d..7b2e1af 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
@@ -19,6 +19,12 @@
 import android.os.Trace
 import android.os.TraceNameSupplier
 import java.util.concurrent.atomic.AtomicInteger
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.async
 
 /**
  * Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection]
@@ -85,5 +91,18 @@
                 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, cookie)
             }
         }
+
+        /**
+         * Convenience method to avoid one indentation level when we want to add a trace when
+         * launching a coroutine
+         */
+        fun <T> CoroutineScope.tracedAsync(
+            method: String,
+            context: CoroutineContext = EmptyCoroutineContext,
+            start: CoroutineStart = CoroutineStart.DEFAULT,
+            block: suspend () -> T
+        ): Deferred<T> {
+            return async(context, start) { traceAsync(method) { block() } }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index ba8e427..f6a0563 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -184,6 +184,10 @@
         }
     }
 
+    public boolean getSplitShadeCentered() {
+        return mSplitShadeCentered;
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 29414ea..5646abe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -234,6 +234,10 @@
         }
     }
 
+    public KeyguardClockSwitch getView() {
+        return mView;
+    }
+
     private void hideSliceViewAndNotificationIconContainer() {
         View ksv = mView.findViewById(R.id.keyguard_slice_view);
         ksv.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 625c1de..b2287d87 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -163,7 +163,10 @@
             }
             mCurrentUser = KeyguardUpdateMonitor.getCurrentUser();
             showPrimarySecurityScreen(false);
-            reinflateViewFlipper((l) -> {});
+            if (mCurrentSecurityMode != SecurityMode.SimPin
+                    && mCurrentSecurityMode != SecurityMode.SimPuk) {
+                reinflateViewFlipper((l) -> {});
+            }
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index d848602..f9cc03ee 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -18,6 +18,7 @@
 
 import static java.util.Collections.emptySet;
 
+import android.animation.LayoutTransition;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.os.Build;
@@ -78,6 +79,14 @@
         mKeyguardSlice = findViewById(R.id.keyguard_slice_view);
 
         mMediaHostContainer = findViewById(R.id.status_view_media_container);
+        if (mMediaHostContainer != null) {
+            LayoutTransition mediaLayoutTransition = new LayoutTransition();
+            ((ViewGroup) mMediaHostContainer).setLayoutTransition(mediaLayoutTransition);
+            mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
+            mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+            mediaLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
+            mediaLayoutTransition.disableTransitionType(LayoutTransition.DISAPPEARING);
+        }
 
         updateDark();
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 8d0d299..931ba6d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -23,6 +23,7 @@
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.animation.Animator;
+import android.animation.LayoutTransition;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.content.res.Configuration;
@@ -101,6 +102,7 @@
     private final Rect mClipBounds = new Rect();
     private final KeyguardInteractor mKeyguardInteractor;
 
+    private Boolean mSplitShadeEnabled = false;
     private Boolean mStatusViewCentered = true;
 
     private DumpManager mDumpManager;
@@ -150,6 +152,48 @@
     @Override
     public void onInit() {
         mKeyguardClockSwitchController.init();
+        final View mediaHostContainer = mView.findViewById(R.id.status_view_media_container);
+        if (mediaHostContainer != null) {
+            mKeyguardClockSwitchController.getView().addOnLayoutChangeListener(
+                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                        if (!mSplitShadeEnabled
+                                || mKeyguardClockSwitchController.getView().getSplitShadeCentered()
+                                // Note: isKeyguardVisible() returns false after Launcher -> AOD.
+                                || !mKeyguardUpdateMonitor.isKeyguardVisible()) {
+                            return;
+                        }
+
+                        int oldHeight = oldBottom - oldTop;
+                        if (v.getHeight() == oldHeight) return;
+
+                        if (mediaHostContainer.getVisibility() != View.VISIBLE
+                                // If the media is appearing, also don't do the transition.
+                                || mediaHostContainer.getHeight() == 0) {
+                            return;
+                        }
+
+                        final LayoutTransition mediaLayoutTransition =
+                                ((ViewGroup) mediaHostContainer).getLayoutTransition();
+                        if (mediaLayoutTransition == null) return;
+
+                        mediaLayoutTransition.enableTransitionType(LayoutTransition.CHANGING);
+                    });
+
+            mediaHostContainer.addOnLayoutChangeListener(
+                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                        final LayoutTransition mediaLayoutTransition =
+                                ((ViewGroup) mediaHostContainer).getLayoutTransition();
+                        if (mediaLayoutTransition == null) return;
+                        if (!mediaLayoutTransition.isTransitionTypeEnabled(
+                                LayoutTransition.CHANGING)) {
+                            return;
+                        }
+                        // Note: when this is called, the LayoutTransition is already been set up.
+                        // Disables the LayoutTransition until it's explicitly enabled again.
+                        mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGING);
+                    }
+            );
+        }
 
         mDumpManager.registerDumpable(getInstanceName(), this);
         if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
@@ -385,6 +429,7 @@
      */
     public void setSplitShadeEnabled(boolean enabled) {
         mKeyguardClockSwitchController.setSplitShadeEnabled(enabled);
+        mSplitShadeEnabled = enabled;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 954129e..22bd207 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -18,8 +18,8 @@
 
 import static androidx.dynamicanimation.animation.DynamicAnimation.TRANSLATION_X;
 import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat;
-
 import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
+import static com.android.systemui.flags.Flags.SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX;
 import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
 
 import android.animation.Animator;
@@ -482,7 +482,14 @@
                 boolean wasRemoved = false;
                 if (animView instanceof ExpandableNotificationRow) {
                     ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
-                    wasRemoved = row.isRemoved();
+                    if (mFeatureFlags.isEnabled(SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX)) {
+                        // If the view is already removed from its parent and added as Transient,
+                        // we need to clean the transient view upon animation end
+                        wasRemoved = row.getTransientContainer() != null
+                            || row.getParent() == null || row.isRemoved();
+                    } else {
+                        wasRemoved = row.isRemoved();
+                    }
                 }
                 if (!mCancelled || wasRemoved) {
                     mCallback.onChildDismissed(animView);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 3b70555..871257e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -303,6 +303,11 @@
     @JvmField val MIGRATE_CLOCKS_TO_BLUEPRINT =
             unreleasedFlag("migrate_clocks_to_blueprint")
 
+    /** Migrate KeyguardRootView to use composables. */
+    // TODO(b/301969856): Tracking Bug.
+    @JvmField val KEYGUARD_ROOT_VIEW_USE_COMPOSE =
+        unreleasedFlag("keyguard_root_view_use_compose")
+
     /** Enables preview loading animation in the wallpaper picker. */
     // TODO(b/274443705): Tracking Bug
     @JvmField
@@ -771,6 +776,10 @@
     // TODO(b/302087895): Tracking Bug
     @JvmField val CALL_LAYOUT_ASYNC_SET_DATA = unreleasedFlag("call_layout_async_set_data")
 
+    // TODO(b/302144438): Tracking Bug
+    @JvmField val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE =
+            unreleasedFlag("decouple_remote_input_delegate_and_callback_update")
+
     // 2900 - CentralSurfaces-related flags
 
     // TODO(b/285174336): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index fb02c7d..1761ca8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -26,7 +26,6 @@
 import com.android.keyguard.LockIconViewController
 import com.android.keyguard.dagger.KeyguardStatusViewComponent
 import com.android.systemui.CoreStartable
-import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -40,7 +39,9 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
+import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationShadeWindowView
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -69,6 +70,7 @@
     private val context: Context,
     private val keyguardIndicationController: KeyguardIndicationController,
     private val lockIconViewController: LockIconViewController,
+    private val shadeInteractor: ShadeInteractor,
 ) : CoreStartable {
 
     private var rootViewHandle: DisposableHandle? = null
@@ -135,6 +137,7 @@
                 occludingAppDeviceEntryMessageViewModel,
                 chipbarCoordinator,
                 keyguardStateController,
+                shadeInteractor
             )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index dd7eee9..abc30ef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -25,6 +25,8 @@
 import android.view.ViewGroup
 import android.view.ViewPropertyAnimator
 import android.widget.ImageView
+import androidx.core.animation.CycleInterpolator
+import androidx.core.animation.ObjectAnimator
 import androidx.core.view.isInvisible
 import androidx.core.view.isVisible
 import androidx.core.view.updateLayoutParams
@@ -383,6 +385,27 @@
                     falsingManager,
                 )
                 view.setOnTouchListener(onTouchListener)
+                view.setOnClickListener {
+                    messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short)
+                    val amplitude =
+                        view.context.resources
+                            .getDimensionPixelSize(R.dimen.keyguard_affordance_shake_amplitude)
+                            .toFloat()
+                    val shakeAnimator =
+                        ObjectAnimator.ofFloat(
+                            view,
+                            "translationX",
+                            -amplitude / 2,
+                            amplitude / 2,
+                        )
+                    shakeAnimator.duration =
+                        KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds
+                    shakeAnimator.interpolator =
+                        CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles)
+                    shakeAnimator.start()
+
+                    vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
+                }
                 view.onLongClickListener =
                     OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener)
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
index 125e2da..f2d39da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
@@ -99,41 +99,7 @@
                     // When not using a stylus, lifting the finger/pointer will actually cancel
                     // the long-press gesture. Calling cancel after the quick affordance was
                     // already long-press activated is a no-op, so it's safe to call from here.
-                    cancel(
-                        onAnimationEnd =
-                            if (event.eventTime - event.downTime < longPressDurationMs) {
-                                Runnable {
-                                    messageDisplayer.invoke(
-                                        R.string.keyguard_affordance_press_too_short
-                                    )
-                                    val amplitude =
-                                        view.context.resources
-                                            .getDimensionPixelSize(
-                                                R.dimen.keyguard_affordance_shake_amplitude
-                                            )
-                                            .toFloat()
-                                    val shakeAnimator =
-                                        ObjectAnimator.ofFloat(
-                                            view,
-                                            "translationX",
-                                            -amplitude / 2,
-                                            amplitude / 2,
-                                        )
-                                    shakeAnimator.duration =
-                                        KeyguardBottomAreaVibrations.ShakeAnimationDuration
-                                            .inWholeMilliseconds
-                                    shakeAnimator.interpolator =
-                                        CycleInterpolator(
-                                            KeyguardBottomAreaVibrations.ShakeAnimationCycles
-                                        )
-                                    shakeAnimator.start()
-
-                                    vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
-                                }
-                            } else {
-                                null
-                            }
-                    )
+                    cancel()
                 }
                 false
             }
@@ -168,10 +134,10 @@
         view.setOnClickListener(null)
     }
 
-    fun cancel(onAnimationEnd: Runnable? = null) {
+    fun cancel() {
         longPressAnimator?.cancel()
         longPressAnimator = null
-        view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd)
+        view.animate().scaleX(1f).scaleY(1f)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index eeb4ac3..aa76702 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -23,6 +23,8 @@
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
+import androidx.core.animation.CycleInterpolator
+import androidx.core.animation.ObjectAnimator
 import androidx.core.view.isInvisible
 import androidx.core.view.isVisible
 import androidx.core.view.updateLayoutParams
@@ -216,6 +218,27 @@
                     falsingManager,
                 )
                 view.setOnTouchListener(onTouchListener)
+                view.setOnClickListener {
+                    messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short)
+                    val amplitude =
+                        view.context.resources
+                            .getDimensionPixelSize(R.dimen.keyguard_affordance_shake_amplitude)
+                            .toFloat()
+                    val shakeAnimator =
+                        ObjectAnimator.ofFloat(
+                            view,
+                            "translationX",
+                            -amplitude / 2,
+                            amplitude / 2,
+                        )
+                    shakeAnimator.duration =
+                        KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds
+                    shakeAnimator.interpolator =
+                        CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles)
+                    shakeAnimator.start()
+
+                    vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
+                }
                 view.onLongClickListener =
                     OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener)
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index f564d00..053727a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.temporarydisplay.ViewPriority
@@ -55,6 +56,7 @@
         occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
         chipbarCoordinator: ChipbarCoordinator,
         keyguardStateController: KeyguardStateController,
+        shadeInteractor: ShadeInteractor,
     ): DisposableHandle {
         val disposableHandle =
             view.repeatWhenAttached {
@@ -88,6 +90,17 @@
                             }
                         }
                     }
+
+                    launch {
+                        shadeInteractor.isAnyFullyExpanded.collect { isFullyAnyExpanded ->
+                            view.visibility =
+                                if (isFullyAnyExpanded) {
+                                    View.INVISIBLE
+                                } else {
+                                    View.VISIBLE
+                                }
+                        }
+                    }
                 }
 
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 2ad74fb..864e345 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -62,6 +62,7 @@
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.shared.clocks.DefaultClockController
 import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
@@ -109,6 +110,7 @@
     private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
     private val chipbarCoordinator: ChipbarCoordinator,
     private val keyguardStateController: KeyguardStateController,
+    private val shadeInteractor: ShadeInteractor,
 ) {
 
     val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
@@ -317,6 +319,7 @@
                 occludingAppDeviceEntryMessageViewModel,
                 chipbarCoordinator,
                 keyguardStateController,
+                shadeInteractor,
             )
         )
         rootView.addView(
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 6c2ce7f..1943b34 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -29,6 +29,7 @@
 import com.android.systemui.log.LogcatEchoTrackerProd;
 import com.android.systemui.log.table.TableLogBuffer;
 import com.android.systemui.log.table.TableLogBufferFactory;
+import com.android.systemui.qs.QSFragmentLegacy;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.util.Compile;
 import com.android.systemui.util.wakelock.WakeLockLog;
@@ -229,12 +230,12 @@
     }
 
     /**
-     * Provides a logging buffer for logs related to {@link com.android.systemui.qs.QSFragment}'s
+     * Provides a logging buffer for logs related to {@link QSFragmentLegacy}'s
      * disable flag adjustments.
      */
     @Provides
     @SysUISingleton
-    @QSFragmentDisableLog
+    @QSDisableLog
     public static LogBuffer provideQSFragmentDisableLogBuffer(LogBufferFactory factory) {
         return factory.create("QSFragmentDisableFlagsLog", 10 /* maxSize */,
                 false /* systrace */);
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSDisableLog.java
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
rename to packages/SystemUI/src/com/android/systemui/log/dagger/QSDisableLog.java
index 557a254..b3bceca 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSDisableLog.java
@@ -19,6 +19,7 @@
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import com.android.systemui.log.LogBuffer;
+import com.android.systemui.qs.QSFragmentLegacy;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
@@ -27,10 +28,10 @@
 
 /**
  * A {@link LogBuffer} for disable flag adjustments made in
- * {@link com.android.systemui.qs.QSFragment}.
+ * {@link QSFragmentLegacy}.
  */
 @Qualifier
 @Documented
 @Retention(RUNTIME)
-public @interface QSFragmentDisableLog {
+public @interface QSDisableLog {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt
index fbf9294..11d0be5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt
@@ -14,20 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.mediaprojection
 
 import android.os.IBinder
 import android.os.Parcel
 import android.os.Parcelable
 
 /**
- * Class that represents an area that should be captured.
- * Currently it has only a launch cookie that represents a task but
- * we potentially could add more identifiers e.g. for a pair of tasks.
+ * Class that represents an area that should be captured. Currently it has only a launch cookie that
+ * represents a task but we potentially could add more identifiers e.g. for a pair of tasks.
  */
-data class MediaProjectionCaptureTarget(
-    val launchCookie: IBinder?
-): Parcelable {
+data class MediaProjectionCaptureTarget(val launchCookie: IBinder?) : Parcelable {
 
     constructor(parcel: Parcel) : this(parcel.readStrongBinder())
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionServiceHelper.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/media/MediaProjectionServiceHelper.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
index 9e616e2..f1cade7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionServiceHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.mediaprojection
 
 import android.content.Context
 import android.media.projection.IMediaProjection
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
index 88bc064..b5d3e91 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.media
+package com.android.systemui.mediaprojection.appselector
 
 import android.app.ActivityOptions
 import android.content.Intent
@@ -46,13 +46,11 @@
 import com.android.internal.widget.RecyclerView
 import com.android.internal.widget.RecyclerViewAccessibilityDelegate
 import com.android.internal.widget.ResolverDrawerLayout
-import com.android.systemui.res.R
-import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent
-import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
-import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
-import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorView
+import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget
+import com.android.systemui.mediaprojection.MediaProjectionServiceHelper
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.AsyncActivityLauncher
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 33d9cc3..72aea04 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -23,8 +23,6 @@
 import androidx.lifecycle.DefaultLifecycleObserver
 import com.android.launcher3.icons.IconFactory
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.media.MediaProjectionAppSelectorActivity
-import com.android.systemui.media.MediaProjectionPermissionActivity
 import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader
 import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
 import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
@@ -37,6 +35,7 @@
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
 import com.android.systemui.mediaprojection.devicepolicy.MediaProjectionDevicePolicyModule
 import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
+import com.android.systemui.mediaprojection.permission.MediaProjectionPermissionActivity
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
 import com.android.systemui.statusbar.policy.ConfigurationController
 import dagger.Binds
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt
index 64006fe..8b437c3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.screenrecord
+package com.android.systemui.mediaprojection.permission
 
 import android.content.Context
 import android.os.Bundle
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 4de6278..2b56d0c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media;
+package com.android.systemui.mediaprojection.permission;
 
 import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT;
 import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT;
@@ -22,8 +22,8 @@
 import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
-import static com.android.systemui.screenrecord.ScreenShareOptionKt.ENTIRE_SCREEN;
-import static com.android.systemui.screenrecord.ScreenShareOptionKt.SINGLE_APP;
+import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.ENTIRE_SCREEN;
+import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.SINGLE_APP;
 
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -51,13 +51,13 @@
 import android.util.Log;
 import android.view.Window;
 
-import com.android.systemui.res.R;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionServiceHelper;
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
-import com.android.systemui.screenrecord.MediaProjectionPermissionDialog;
-import com.android.systemui.screenrecord.ScreenShareOption;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.Utils;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
index 47e28d8..2f10ad3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.screenrecord
+package com.android.systemui.mediaprojection.permission
 
 import android.content.Context
 import android.media.projection.MediaProjectionConfig
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
index ebf0dd2..37e8d9f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.screenrecord
+package com.android.systemui.mediaprojection.permission
 
 import androidx.annotation.IntDef
 import androidx.annotation.StringRes
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 463c79c..eba1c25 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -28,7 +28,7 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.dagger.qualifiers.RootView;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTileView;
 import com.android.systemui.qs.QSPanel.QSTileLayout;
@@ -86,8 +86,7 @@
     private final QuickQSPanel mQuickQsPanel;
     private final QSPanelController mQsPanelController;
     private final QuickQSPanelController mQuickQSPanelController;
-    private final QuickStatusBarHeader mQuickStatusBarHeader;
-    private final QS mQs;
+    private final View mQsRootView;
 
     @Nullable
     private PagedTileLayout mPagedLayout;
@@ -115,8 +114,6 @@
     // Brightness slider opacity driver. Uses linear interpolator.
     @Nullable
     private TouchAnimator mBrightnessOpacityAnimator;
-    // Animator for Footer actions in QQS
-    private TouchAnimator mQQSFooterActionsAnimator;
     // Height animator for QQS tiles (height changing from QQS size to QS size)
     @Nullable
     private HeightExpansionAnimator mQQSTileHeightAnimator;
@@ -144,22 +141,21 @@
     private int[] mTmpLoc2 = new int[2];
 
     @Inject
-    public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader,
+    public QSAnimator(@RootView View rootView, QuickQSPanel quickPanel,
             QSPanelController qsPanelController,
             QuickQSPanelController quickQSPanelController, QSHost qsTileHost,
             @Main Executor executor, TunerService tunerService,
             QSExpansionPathInterpolator qsExpansionPathInterpolator) {
-        mQs = qs;
+        mQsRootView = rootView;
         mQuickQsPanel = quickPanel;
         mQsPanelController = qsPanelController;
         mQuickQSPanelController = quickQSPanelController;
-        mQuickStatusBarHeader = quickStatusBarHeader;
         mHost = qsTileHost;
         mExecutor = executor;
         mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
         mHost.addCallback(this);
         mQsPanelController.addOnAttachStateChangeListener(this);
-        qs.getView().addOnLayoutChangeListener(this);
+        mQsRootView.addOnLayoutChangeListener(this);
         if (mQsPanelController.isAttachedToWindow()) {
             onViewAttachedToWindow(null);
         }
@@ -314,8 +310,7 @@
                     break;
                 }
 
-                final View tileIcon = tileView.getIcon().getIconView();
-                View view = mQs.getView();
+                View view = mQsRootView;
 
                 // This case: less tiles to animate in small displays.
                 if (count < mQuickQSPanelController.getTileLayout().getNumVisibleTiles()) {
@@ -480,7 +475,7 @@
                 .setStartDelay(QS_TILE_LABEL_FADE_OUT_START)
                 .setEndDelay(QS_TILE_LABEL_FADE_OUT_END);
         SideLabelTileLayout qqsLayout = (SideLabelTileLayout) mQuickQsPanel.getTileLayout();
-        View view = mQs.getView();
+        View view = mQsRootView;
         List<String> specs = mPagedLayout.getSpecsForPage(page);
         if (specs.isEmpty()) {
             // specs should not be empty in a valid secondary page, as we scrolled to it.
@@ -577,7 +572,7 @@
 
             // For (1), compute the distance via the vertical distance between QQS and QS tile
             // layout top.
-            View quickSettingsRootView = mQs.getView();
+            View quickSettingsRootView = mQsRootView;
             View qsTileLayout = (View) mQsPanelController.getTileLayout();
             View qqsTileLayout = (View) mQuickQSPanelController.getTileLayout();
             getRelativePosition(mTmpLoc1, qsTileLayout, quickSettingsRootView);
@@ -607,7 +602,7 @@
     private int getRelativeTranslationY(View view1, View view2) {
         int[] qsPosition = new int[2];
         int[] qqsPosition = new int[2];
-        View commonView = mQs.getView();
+        View commonView = mQsRootView;
         getRelativePositionInt(qsPosition, view1, commonView);
         getRelativePositionInt(qqsPosition, view2, commonView);
         return qsPosition[1] - qqsPosition[1];
@@ -690,9 +685,6 @@
         if (mBrightnessTranslationAnimator != null) {
             mBrightnessTranslationAnimator.setPosition(position);
         }
-        if (mQQSFooterActionsAnimator != null) {
-            mQQSFooterActionsAnimator.setPosition(position);
-        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSDisableFlagsLogger.kt
similarity index 73%
rename from packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
rename to packages/SystemUI/src/com/android/systemui/qs/QSDisableFlagsLogger.kt
index 6563e42..6f6f467 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDisableFlagsLogger.kt
@@ -1,20 +1,22 @@
 package com.android.systemui.qs
 
-import com.android.systemui.log.dagger.QSFragmentDisableLog
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.QSDisableLog
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
 import javax.inject.Inject
 
-/** A helper class for logging disable flag changes made in [QSFragment]. */
-class QSFragmentDisableFlagsLogger @Inject constructor(
-    @QSFragmentDisableLog private val buffer: LogBuffer,
+/** A helper class for logging disable flag changes made in [QSImpl]. */
+class QSDisableFlagsLogger
+@Inject
+constructor(
+    @QSDisableLog private val buffer: LogBuffer,
     private val disableFlagsLogger: DisableFlagsLogger
 ) {
 
     /**
-     * Logs a string representing the new state received by [QSFragment] and any modifications that
-     * were made to the flags locally.
+     * Logs a string representing the new state received by [QSImpl] and any modifications that were
+     * made to the flags locally.
      *
      * @param new see [DisableFlagsLogger.getDisableFlagsString]
      * @param newAfterLocalModification see [DisableFlagsLogger.getDisableFlagsString]
@@ -43,4 +45,4 @@
     }
 }
 
-private const val TAG = "QSFragmentDisableFlagsLog"
+private const val TAG = "QSDisableFlagsLog"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
new file mode 100644
index 0000000..8589ae9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
@@ -0,0 +1,369 @@
+/*
+ * 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.systemui.qs;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Trace;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.FloatRange;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.plugins.qs.QSContainerController;
+import com.android.systemui.qs.dagger.QSFragmentComponent;
+import com.android.systemui.res.R;
+import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.util.LifecycleFragment;
+
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+public class QSFragmentLegacy extends LifecycleFragment implements QS {
+
+    private final Provider<QSImpl> mQsImplProvider;
+
+    private final QSFragmentComponent.Factory mQsComponentFactory;
+
+    @Nullable
+    private QSImpl mQsImpl;
+
+    @Inject
+    public QSFragmentLegacy(
+            Provider<QSImpl> qsImplProvider,
+            QSFragmentComponent.Factory qsComponentFactory
+    ) {
+        mQsComponentFactory = qsComponentFactory;
+        mQsImplProvider = qsImplProvider;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            Bundle savedInstanceState) {
+        try {
+            Trace.beginSection("QSFragment#onCreateView");
+            inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(),
+                    R.style.Theme_SystemUI_QuickSettings));
+            return inflater.inflate(R.layout.qs_panel, container, false);
+        } finally {
+            Trace.endSection();
+        }
+    }
+
+    @Override
+    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+        QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this);
+        mQsImpl = mQsImplProvider.get();
+        mQsImpl.onComponentCreated(qsFragmentComponent, savedInstanceState);
+    }
+
+    @Override
+    public void setScrollListener(ScrollListener listener) {
+        if (mQsImpl != null) {
+            mQsImpl.setScrollListener(listener);
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (mQsImpl != null) {
+            mQsImpl.onCreate(savedInstanceState);
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mQsImpl != null) {
+            mQsImpl.onDestroy();
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        if (mQsImpl != null) {
+            mQsImpl.onSaveInstanceState(outState);
+        }
+    }
+
+    @Override
+    public View getHeader() {
+        if (mQsImpl != null) {
+            return mQsImpl.getHeader();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void setHasNotifications(boolean hasNotifications) {
+        if (mQsImpl != null) {
+            mQsImpl.setHasNotifications(hasNotifications);
+        }
+    }
+
+    @Override
+    public void setPanelView(HeightListener panelView) {
+        if (mQsImpl != null) {
+            mQsImpl.setPanelView(panelView);
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (mQsImpl != null) {
+            mQsImpl.onConfigurationChanged(newConfig);
+        }
+    }
+
+    @Override
+    public void setFancyClipping(int leftInset, int top, int rightInset, int bottom,
+            int cornerRadius, boolean visible, boolean fullWidth) {
+        if (mQsImpl != null) {
+            mQsImpl.setFancyClipping(leftInset, top, rightInset, bottom, cornerRadius, visible,
+                    fullWidth);
+        }
+    }
+
+    @Override
+    public boolean isFullyCollapsed() {
+        if (mQsImpl != null) {
+            return mQsImpl.isFullyCollapsed();
+        } else {
+            return true;
+        }
+    }
+
+    @Override
+    public void setCollapsedMediaVisibilityChangedListener(Consumer<Boolean> listener) {
+        if (mQsImpl != null) {
+            mQsImpl.setCollapsedMediaVisibilityChangedListener(listener);
+        }
+    }
+
+    @Override
+    public void setContainerController(QSContainerController controller) {
+        if (mQsImpl != null) {
+            mQsImpl.setContainerController(controller);
+        }
+    }
+
+    @Override
+    public boolean isCustomizing() {
+        if (mQsImpl != null) {
+            return mQsImpl.isCustomizing();
+        } else {
+            return false;
+        }
+    }
+
+    public QSPanelController getQSPanelController() {
+        if (mQsImpl != null) {
+            return mQsImpl.getQSPanelController();
+        } else {
+            return null;
+        }
+    }
+
+    public void setBrightnessMirrorController(
+            BrightnessMirrorController brightnessMirrorController) {
+        if (mQsImpl != null) {
+            mQsImpl.setBrightnessMirrorController(brightnessMirrorController);
+        }
+    }
+
+    @Override
+    public boolean isShowingDetail() {
+        if (mQsImpl != null) {
+            return mQsImpl.isShowingDetail();
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public void setHeaderClickable(boolean clickable) {
+        if (mQsImpl != null) {
+            mQsImpl.setHeaderClickable(clickable);
+        }
+    }
+
+    @Override
+    public void setExpanded(boolean expanded) {
+        if (mQsImpl != null) {
+            mQsImpl.setExpanded(expanded);
+        }
+    }
+
+    @Override
+    public void setOverscrolling(boolean stackScrollerOverscrolling) {
+        if (mQsImpl != null) {
+            mQsImpl.setOverscrolling(stackScrollerOverscrolling);
+        }
+    }
+
+    @Override
+    public void setListening(boolean listening) {
+        if (mQsImpl != null) {
+            mQsImpl.setListening(listening);
+        }
+    }
+
+    @Override
+    public void setQsVisible(boolean visible) {
+        if (mQsImpl != null) {
+            mQsImpl.setQsVisible(visible);
+        }
+    }
+
+    @Override
+    public void setHeaderListening(boolean listening) {
+        if (mQsImpl != null) {
+            mQsImpl.setHeaderListening(listening);
+        }
+    }
+
+    @Override
+    public void notifyCustomizeChanged() {
+        if (mQsImpl != null) {
+            mQsImpl.notifyCustomizeChanged();
+        }
+    }
+
+    @Override
+    public void setInSplitShade(boolean inSplitShade) {
+        if (mQsImpl != null) {
+            mQsImpl.setInSplitShade(inSplitShade);
+        }
+    }
+
+    @Override
+    public void setTransitionToFullShadeProgress(
+            boolean isTransitioningToFullShade,
+            @FloatRange(from = 0.0, to = 1.0) float qsTransitionFraction,
+            @FloatRange(from = 0.0, to = 1.0) float qsSquishinessFraction) {
+        if (mQsImpl != null) {
+            mQsImpl.setTransitionToFullShadeProgress(isTransitioningToFullShade,
+                    qsTransitionFraction, qsSquishinessFraction);
+        }
+    }
+
+    @Override
+    public void setOverScrollAmount(int overScrollAmount) {
+        if (mQsImpl != null) {
+            mQsImpl.setOverScrollAmount(overScrollAmount);
+        }
+    }
+
+    @Override
+    public int getHeightDiff() {
+        if (mQsImpl != null) {
+            return mQsImpl.getHeightDiff();
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public void setIsNotificationPanelFullWidth(boolean isFullWidth) {
+        if (mQsImpl != null) {
+            mQsImpl.setIsNotificationPanelFullWidth(isFullWidth);
+        }
+    }
+
+    @Override
+    public void setQsExpansion(float expansion, float panelExpansionFraction,
+            float proposedTranslation, float squishinessFraction) {
+        if (mQsImpl != null) {
+            mQsImpl.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+                    squishinessFraction);
+        }
+    }
+
+    @Override
+    public void animateHeaderSlidingOut() {
+        if (mQsImpl != null) {
+            mQsImpl.animateHeaderSlidingOut();
+        }
+    }
+
+    @Override
+    public void setCollapseExpandAction(Runnable action) {
+        if (mQsImpl != null) {
+            mQsImpl.setCollapseExpandAction(action);
+        }
+    }
+
+    @Override
+    public void closeDetail() {
+        if (mQsImpl != null) {
+            mQsImpl.closeDetail();
+        }
+    }
+
+    @Override
+    public void closeCustomizer() {
+        if (mQsImpl != null) {
+            mQsImpl.closeDetail();
+        }
+    }
+
+    /**
+     * The height this view wants to be. This is different from {@link View#getMeasuredHeight} such
+     * that during closing the detail panel, this already returns the smaller height.
+     */
+    @Override
+    public int getDesiredHeight() {
+        if (mQsImpl != null) {
+            return mQsImpl.getDesiredHeight();
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public void setHeightOverride(int desiredHeight) {
+        if (mQsImpl != null) {
+            mQsImpl.setHeightOverride(desiredHeight);
+        }
+    }
+
+    @Override
+    public int getQsMinExpansionHeight() {
+        if (mQsImpl != null) {
+            return mQsImpl.getQsMinExpansionHeight();
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public void hideImmediately() {
+        if (mQsImpl != null) {
+            mQsImpl.hideImmediately();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
index 253560b..9fa6769 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
@@ -31,10 +31,13 @@
 @Inject
 constructor(
     private val fragmentService: FragmentService,
-    private val qsFragmentProvider: Provider<QSFragment>
+    private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy>
 ) : CoreStartable {
     override fun start() {
-        fragmentService.addFragmentInstantiationProvider(QSFragment::class.java, qsFragmentProvider)
+        fragmentService.addFragmentInstantiationProvider(
+            QSFragmentLegacy::class.java,
+            qsFragmentLegacyProvider
+        )
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
rename to packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index fd81e9a..a32a024 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -1,15 +1,17 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
+ * 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.
+ * 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.systemui.qs;
@@ -20,21 +22,18 @@
 import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
-import static com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.os.Trace;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
-import android.view.ContextThemeWrapper;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 import android.widget.LinearLayout;
 
 import androidx.annotation.FloatRange;
@@ -47,7 +46,6 @@
 import com.android.app.animation.Interpolators;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.dump.DumpManager;
@@ -58,19 +56,20 @@
 import com.android.systemui.plugins.qs.QSContainerController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.customize.QSCustomizerController;
-import com.android.systemui.qs.dagger.QSFragmentComponent;
+import com.android.systemui.qs.dagger.QSComponent;
 import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.util.LifecycleFragment;
 import com.android.systemui.util.Utils;
 
 import java.io.PrintWriter;
@@ -80,8 +79,8 @@
 import javax.inject.Inject;
 import javax.inject.Named;
 
-public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Callbacks,
-        StatusBarStateController.StateListener, Dumpable {
+public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateController.StateListener,
+        Dumpable {
     private static final String TAG = "QS";
     private static final boolean DEBUG = false;
     private static final String EXTRA_EXPANDED = "expanded";
@@ -113,8 +112,7 @@
     private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
     private final MediaHost mQsMediaHost;
     private final MediaHost mQqsMediaHost;
-    private final QSFragmentComponent.Factory mQsComponentFactory;
-    private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger;
+    private final QSDisableFlagsLogger mQsDisableFlagsLogger;
     private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
     private final FeatureFlags mFeatureFlags;
     private final QSLogger mLogger;
@@ -167,14 +165,17 @@
 
     private boolean mIsSmallScreen;
 
+    private CommandQueue mCommandQueue;
+
+    private View mRootView;
+
     @Inject
-    public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
+    public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
             SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue,
             @Named(QS_PANEL) MediaHost qsMediaHost,
             @Named(QUICK_QS_PANEL) MediaHost qqsMediaHost,
             KeyguardBypassController keyguardBypassController,
-            QSFragmentComponent.Factory qsComponentFactory,
-            QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger,
+            QSDisableFlagsLogger qsDisableFlagsLogger,
             DumpManager dumpManager, QSLogger qsLogger,
             FooterActionsController footerActionsController,
             FooterActionsViewModel.Factory footerActionsViewModelFactory,
@@ -184,12 +185,11 @@
         mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
         mQsMediaHost = qsMediaHost;
         mQqsMediaHost = qqsMediaHost;
-        mQsComponentFactory = qsComponentFactory;
-        mQsFragmentDisableFlagsLogger = qsFragmentDisableFlagsLogger;
+        mQsDisableFlagsLogger = qsDisableFlagsLogger;
         mLogger = qsLogger;
         mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
         mFeatureFlags = featureFlags;
-        commandQueue.observe(getLifecycle(), this);
+        mCommandQueue = commandQueue;
         mBypassController = keyguardBypassController;
         mStatusBarStateController = statusBarStateController;
         mDumpManager = dumpManager;
@@ -199,34 +199,23 @@
         mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
     }
 
-    @Override
-    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
-            Bundle savedInstanceState) {
-        try {
-            Trace.beginSection("QSFragment#onCreateView");
-            inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(),
-                    R.style.Theme_SystemUI_QuickSettings));
-            return inflater.inflate(R.layout.qs_panel, container, false);
-        } finally {
-            Trace.endSection();
-        }
-    }
+    public void onComponentCreated(QSComponent qsComponent, @Nullable Bundle savedInstanceState) {
+        mRootView = qsComponent.getRootView();
 
-    @Override
-    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
-        QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this);
-        mQSPanelController = qsFragmentComponent.getQSPanelController();
-        mQuickQSPanelController = qsFragmentComponent.getQuickQSPanelController();
+        mCommandQueue.addCallback(this);
+
+        mQSPanelController = qsComponent.getQSPanelController();
+        mQuickQSPanelController = qsComponent.getQuickQSPanelController();
 
         mQSPanelController.init();
         mQuickQSPanelController.init();
 
-        mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */
-                this);
-        bindFooterActionsView(view);
+        mQSFooterActionsViewModel = mFooterActionsViewModelFactory
+                .create(mListeningAndVisibilityLifecycleOwner);
+        bindFooterActionsView(mRootView);
         mFooterActionsController.init();
 
-        mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
+        mQSPanelScrollView = mRootView.findViewById(R.id.expanded_qs_scroll_view);
         mQSPanelScrollView.addOnLayoutChangeListener(
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                     updateQsBounds();
@@ -238,26 +227,26 @@
                     if (mScrollListener != null) {
                         mScrollListener.onQsPanelScrollChanged(scrollY);
                     }
-        });
-        mHeader = view.findViewById(R.id.header);
-        mFooter = qsFragmentComponent.getQSFooter();
+                });
+        mHeader = mRootView.findViewById(R.id.header);
+        mFooter = qsComponent.getQSFooter();
 
-        mQSContainerImplController = qsFragmentComponent.getQSContainerImplController();
+        mQSContainerImplController = qsComponent.getQSContainerImplController();
         mQSContainerImplController.init();
         mContainer = mQSContainerImplController.getView();
         mDumpManager.registerDumpable(mContainer.getClass().getSimpleName(), mContainer);
 
-        mQSAnimator = qsFragmentComponent.getQSAnimator();
-        mQSSquishinessController = qsFragmentComponent.getQSSquishinessController();
+        mQSAnimator = qsComponent.getQSAnimator();
+        mQSSquishinessController = qsComponent.getQSSquishinessController();
 
-        mQSCustomizerController = qsFragmentComponent.getQSCustomizerController();
+        mQSCustomizerController = qsComponent.getQSCustomizerController();
         mQSCustomizerController.init();
         mQSCustomizerController.setQs(this);
         if (savedInstanceState != null) {
             setQsVisible(savedInstanceState.getBoolean(EXTRA_VISIBLE));
             setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED));
             setListening(savedInstanceState.getBoolean(EXTRA_LISTENING));
-            setEditLocation(view);
+            setEditLocation(mRootView);
             mQSCustomizerController.restoreInstanceState(savedInstanceState);
             if (mQsExpanded) {
                 mQSPanelController.getTileLayout().restoreInstanceState(savedInstanceState);
@@ -265,7 +254,7 @@
         }
         mStatusBarStateController.addCallback(this);
         onStateChanged(mStatusBarStateController.getState());
-        view.addOnLayoutChangeListener(
+        mRootView.addOnLayoutChangeListener(
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                     boolean sizeChanged = (oldTop - oldBottom) != (top - bottom);
                     if (sizeChanged) {
@@ -327,15 +316,12 @@
         mScrollListener = listener;
     }
 
-    @Override
     public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
         mDumpManager.registerDumpable(getClass().getSimpleName(), this);
     }
 
-    @Override
     public void onDestroy() {
-        super.onDestroy();
+        mCommandQueue.removeCallback(this);
         mStatusBarStateController.removeCallback(this);
         if (mListening) {
             setListening(false);
@@ -351,9 +337,7 @@
         mListeningAndVisibilityLifecycleOwner.destroy();
     }
 
-    @Override
     public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
         outState.putBoolean(EXTRA_EXPANDED, mQsExpanded);
         outState.putBoolean(EXTRA_LISTENING, mListening);
         outState.putBoolean(EXTRA_VISIBLE, mQsVisible);
@@ -394,9 +378,7 @@
         mPanelView = panelView;
     }
 
-    @Override
     public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
         setEditLocation(getView());
         if (newConfig.getLayoutDirection() != mLayoutDirection) {
             mLayoutDirection = newConfig.getLayoutDirection();
@@ -452,9 +434,9 @@
         int state2BeforeAdjustment = state2;
         state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
 
-        mQsFragmentDisableFlagsLogger.logDisableFlagChange(
-                /* new= */ new DisableState(state1, state2BeforeAdjustment),
-                /* newAfterLocalModification= */ new DisableState(state1, state2)
+        mQsDisableFlagsLogger.logDisableFlagChange(
+                /* new= */ new DisableFlagsLogger.DisableState(state1, state2BeforeAdjustment),
+                /* newAfterLocalModification= */ new DisableFlagsLogger.DisableState(state1, state2)
         );
 
         final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
@@ -919,32 +901,6 @@
         getView().setY(-getQsMinExpansionHeight());
     }
 
-    private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
-            = new ViewTreeObserver.OnPreDrawListener() {
-        @Override
-        public boolean onPreDraw() {
-            getView().getViewTreeObserver().removeOnPreDrawListener(this);
-            getView().animate()
-                    .translationY(0f)
-                    .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
-                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                    .setListener(mAnimateHeaderSlidingInListener)
-                    .start();
-            return true;
-        }
-    };
-
-    private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
-            = new AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            mHeaderAnimating = false;
-            updateQsState();
-            // Unset the listener, otherwise this may persist for another view property animation
-            getView().animate().setListener(null);
-        }
-    };
-
     @Override
     public void onUpcomingStateChanged(int upcomingState) {
         if (upcomingState == KEYGUARD) {
@@ -1030,6 +986,20 @@
         return "GONE";
     }
 
+    @Override
+    public View getView() {
+        return mRootView;
+    }
+
+    @Override
+    public Context getContext() {
+        return mRootView.getContext();
+    }
+
+    private Resources getResources() {
+        return getContext().getResources();
+    }
+
     /**
      * A {@link LifecycleOwner} whose state is driven by the current state of this fragment:
      *
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 9359958..6bbdc54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -19,7 +19,7 @@
 import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE;
 import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
 import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
+import static com.android.systemui.qs.dagger.QSScopeModule.QS_USING_MEDIA_PLAYER;
 
 import android.view.MotionEvent;
 import android.view.View;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index ef81674..60c92c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -17,7 +17,6 @@
 package com.android.systemui.qs;
 
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -44,9 +43,6 @@
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.animation.DisappearParameters;
 
-import kotlin.Unit;
-import kotlin.jvm.functions.Function1;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -54,7 +50,8 @@
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
-import javax.inject.Named;
+import kotlin.Unit;
+import kotlin.jvm.functions.Function1;
 
 /**
  * Controller for QSPanel views.
@@ -135,7 +132,7 @@
             T view,
             QSHost host,
             QSCustomizerController qsCustomizerController,
-            @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
+            boolean usingMediaPlayer,
             MediaHost mediaHost,
             MetricsLogger metricsLogger,
             UiEventLogger uiEventLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 9a9626d..10f95e0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -48,6 +48,7 @@
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
+import com.android.systemui.qs.tiles.di.NewQSTileFactory;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
@@ -56,6 +57,8 @@
 import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.settings.SecureSettings;
 
+import dagger.Lazy;
+
 import org.jetbrains.annotations.NotNull;
 
 import java.io.PrintWriter;
@@ -121,6 +124,7 @@
 
     @Inject
     public QSTileHost(Context context,
+            Lazy<NewQSTileFactory> newQsTileFactoryProvider,
             QSFactory defaultFactory,
             @Main Executor mainExecutor,
             PluginManager pluginManager,
@@ -147,6 +151,9 @@
 
         mShadeController = shadeController;
 
+        if (featureFlags.getPipelineTilesEnabled()) {
+            mQsFactories.add(newQsTileFactoryProvider.get());
+        }
         mQsFactories.add(defaultFactory);
         pluginManager.addPluginListener(this, QSFactory.class, true);
         mUserTracker = userTracker;
@@ -326,7 +333,6 @@
                 try {
                     tile = createTile(tileSpec);
                     if (tile != null) {
-                        tile.setTileSpec(tileSpec);
                         if (tile.isAvailable()) {
                             newTiles.put(tileSpec, tile);
                             mQSLogger.logTileAdded(tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 099d19d8..f278dce 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -17,14 +17,13 @@
 package com.android.systemui.qs;
 
 import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_COLLAPSED_LANDSCAPE_MEDIA;
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
+import static com.android.systemui.qs.dagger.QSScopeModule.QS_USING_COLLAPSED_LANDSCAPE_MEDIA;
+import static com.android.systemui.qs.dagger.QSScopeModule.QS_USING_MEDIA_PLAYER;
 
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.media.controls.ui.MediaHost;
@@ -32,6 +31,7 @@
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.util.leak.RotationUtils;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 7888f4c..a103566 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -33,11 +33,11 @@
 import androidx.recyclerview.widget.DefaultItemAnimator;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.android.systemui.res.R;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QSContainerController;
 import com.android.systemui.qs.QSDetailClipper;
 import com.android.systemui.qs.QSUtils;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.LightBarController;
 
 /**
@@ -135,8 +135,10 @@
             setVisibility(View.VISIBLE);
             long duration = mClipper.animateCircularClip(
                     mX, mY, true, new ExpandAnimatorListener(tileAdapter));
-            mQsContainerController.setCustomizerAnimating(true);
-            mQsContainerController.setCustomizerShowing(true, duration);
+            if (mQsContainerController != null) {
+                mQsContainerController.setCustomizerAnimating(true);
+                mQsContainerController.setCustomizerShowing(true, duration);
+            }
         }
     }
 
@@ -150,8 +152,10 @@
             mClipper.showBackground();
             isShown = true;
             setCustomizing(true);
-            mQsContainerController.setCustomizerAnimating(false);
-            mQsContainerController.setCustomizerShowing(true);
+            if (mQsContainerController != null) {
+                mQsContainerController.setCustomizerAnimating(false);
+                mQsContainerController.setCustomizerShowing(true);
+            }
         }
     }
 
@@ -169,8 +173,10 @@
             } else {
                 setVisibility(View.GONE);
             }
-            mQsContainerController.setCustomizerAnimating(animate);
-            mQsContainerController.setCustomizerShowing(false, duration);
+            if (mQsContainerController != null) {
+                mQsContainerController.setCustomizerAnimating(animate);
+                mQsContainerController.setCustomizerShowing(false, duration);
+            }
         }
     }
 
@@ -180,7 +186,9 @@
 
     void setCustomizing(boolean customizing) {
         mCustomizing = customizing;
-        mQs.notifyCustomizeChanged();
+        if (mQs != null) {
+            mQs.notifyCustomizeChanged();
+        }
     }
 
     public boolean isCustomizing() {
@@ -208,15 +216,21 @@
                 setCustomizing(true);
             }
             mOpening = false;
-            mQsContainerController.setCustomizerAnimating(false);
+            if (mQsContainerController != null) {
+                mQsContainerController.setCustomizerAnimating(false);
+            }
             mRecyclerView.setAdapter(mTileAdapter);
         }
 
         @Override
         public void onAnimationCancel(Animator animation) {
             mOpening = false;
-            mQs.notifyCustomizeChanged();
-            mQsContainerController.setCustomizerAnimating(false);
+            if (mQs != null) {
+                mQs.notifyCustomizeChanged();
+            }
+            if (mQsContainerController != null) {
+                mQsContainerController.setCustomizerAnimating(false);
+            }
         }
     }
 
@@ -226,7 +240,9 @@
             if (!isShown) {
                 setVisibility(View.GONE);
             }
-            mQsContainerController.setCustomizerAnimating(false);
+            if (mQsContainerController != null) {
+                mQsContainerController.setCustomizerAnimating(false);
+            }
         }
 
         @Override
@@ -234,7 +250,9 @@
             if (!isShown) {
                 setVisibility(View.GONE);
             }
-            mQsContainerController.setCustomizerAnimating(false);
+            if (mQsContainerController != null) {
+                mQsContainerController.setCustomizerAnimating(false);
+            }
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index ce504b2..024e760 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -34,14 +34,14 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QSContainerController;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.QSEditEvent;
-import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -199,7 +199,7 @@
     }
 
     /** */
-    public void setQs(@Nullable QSFragment qsFragment) {
+    public void setQs(@Nullable QS qsFragment) {
         mView.setQs(qsFragment);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index a6226b3..2af7ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -130,11 +130,9 @@
             if (tile == null) {
                 continue;
             } else if (!tile.isAvailable()) {
-                tile.setTileSpec(spec);
                 tile.destroy();
                 continue;
             }
-            tile.setTileSpec(spec);
             tilesToAdd.add(tile);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSComponent.kt
new file mode 100644
index 0000000..f3413b80
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSComponent.kt
@@ -0,0 +1,40 @@
+package com.android.systemui.qs.dagger
+
+import android.view.View
+import com.android.systemui.dagger.qualifiers.RootView
+import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.QSAnimator
+import com.android.systemui.qs.QSContainerImplController
+import com.android.systemui.qs.QSFooter
+import com.android.systemui.qs.QSPanelController
+import com.android.systemui.qs.QSSquishinessController
+import com.android.systemui.qs.QuickQSPanelController
+import com.android.systemui.qs.customize.QSCustomizerController
+
+interface QSComponent {
+    /** Construct a [QSPanelController]. */
+    fun getQSPanelController(): QSPanelController
+
+    /** Construct a [QuickQSPanelController]. */
+    fun getQuickQSPanelController(): QuickQSPanelController
+
+    /** Construct a [QSAnimator]. */
+    fun getQSAnimator(): QSAnimator
+
+    /** Construct a [QSContainerImplController]. */
+    fun getQSContainerImplController(): QSContainerImplController
+
+    /** Construct a [QSFooter] */
+    fun getQSFooter(): QSFooter
+
+    /** Construct a [QSCustomizerController]. */
+    fun getQSCustomizerController(): QSCustomizerController
+
+    /** Construct a [QSSquishinessController]. */
+    fun getQSSquishinessController(): QSSquishinessController
+
+    /** Construct a [FooterActionsController]. */
+    fun getQSFooterActionController(): FooterActionsController
+
+    @RootView fun getRootView(): View
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt
new file mode 100644
index 0000000..ba1aa62
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.systemui.qs.dagger
+
+import android.view.View
+import com.android.systemui.dagger.qualifiers.RootView
+import dagger.BindsInstance
+import dagger.Subcomponent
+
+@Subcomponent(modules = [QSFlexiglassModule::class])
+@QSScope
+interface QSFlexiglassComponent : QSComponent {
+
+    @Subcomponent.Factory
+    interface Factory {
+        fun create(@RootView @BindsInstance rootView: View): QSFlexiglassComponent
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt
new file mode 100644
index 0000000..36fac44
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.systemui.qs.dagger
+
+import android.content.Context
+import com.android.systemui.qs.dagger.QSScopeModule.Companion.QS_USING_COLLAPSED_LANDSCAPE_MEDIA
+import com.android.systemui.qs.dagger.QSScopeModule.Companion.QS_USING_MEDIA_PLAYER
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+@Module(includes = [QSScopeModule::class])
+interface QSFlexiglassModule {
+
+    @Module
+    companion object {
+
+        /**  */
+        @Provides
+        @Named(QS_USING_MEDIA_PLAYER)
+        @JvmStatic
+        fun providesQSUsingMediaPlayer(context: Context?): Boolean {
+            return false
+        }
+
+        /**  */
+        @Provides
+        @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
+        @JvmStatic
+        fun providesQSUsingCollapsedLandscapeMedia(context: Context): Boolean {
+            return false
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
index 594f4f8..327e858 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
@@ -16,53 +16,21 @@
 
 package com.android.systemui.qs.dagger;
 
-import com.android.systemui.qs.FooterActionsController;
-import com.android.systemui.qs.QSAnimator;
-import com.android.systemui.qs.QSContainerImplController;
-import com.android.systemui.qs.QSFooter;
-import com.android.systemui.qs.QSFragment;
-import com.android.systemui.qs.QSPanelController;
-import com.android.systemui.qs.QSSquishinessController;
-import com.android.systemui.qs.QuickQSPanelController;
-import com.android.systemui.qs.customize.QSCustomizerController;
+import com.android.systemui.qs.QSFragmentLegacy;
 
 import dagger.BindsInstance;
 import dagger.Subcomponent;
 
 /**
- * Dagger Subcomponent for {@link QSFragment}.
+ * Dagger Subcomponent for {@link QSFragmentLegacy}.
  */
 @Subcomponent(modules = {QSFragmentModule.class})
 @QSScope
-public interface QSFragmentComponent {
+public interface QSFragmentComponent extends QSComponent {
 
     /** Factory for building a {@link QSFragmentComponent}. */
     @Subcomponent.Factory
     interface Factory {
-        QSFragmentComponent create(@BindsInstance QSFragment qsFragment);
+        QSFragmentComponent create(@BindsInstance QSFragmentLegacy qsFragment);
     }
-
-    /** Construct a {@link QSPanelController}. */
-    QSPanelController getQSPanelController();
-
-    /** Construct a {@link QuickQSPanelController}. */
-    QuickQSPanelController getQuickQSPanelController();
-
-    /** Construct a {@link QSAnimator}. */
-    QSAnimator getQSAnimator();
-
-    /** Construct a {@link QSContainerImplController}. */
-    QSContainerImplController getQSContainerImplController();
-
-    /** Construct a {@link QSFooter} */
-    QSFooter getQSFooter();
-
-    /** Construct a {@link QSCustomizerController}. */
-    QSCustomizerController getQSCustomizerController();
-
-    /** Construct a {@link QSSquishinessController}. */
-    QSSquishinessController getQSSquishinessController();
-
-    /** Construct a {@link FooterActionsController}. */
-    FooterActionsController getQSFooterActionController();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index bcd9803..0c9c24d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -20,21 +20,11 @@
 import static com.android.systemui.util.Utils.useQsMediaPlayer;
 
 import android.content.Context;
-import android.view.LayoutInflater;
 import android.view.View;
 
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.RootView;
 import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.qs.QSContainerImpl;
-import com.android.systemui.qs.QSFooter;
-import com.android.systemui.qs.QSFooterView;
-import com.android.systemui.qs.QSFooterViewController;
-import com.android.systemui.qs.QSFragment;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.QuickQSPanel;
-import com.android.systemui.qs.QuickStatusBarHeader;
-import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.qs.QSFragmentLegacy;
 
 import javax.inject.Named;
 
@@ -45,93 +35,31 @@
 /**
  * Dagger Module for {@link QSFragmentComponent}.
  */
-@Module
-public interface QSFragmentModule {
-    String QS_USING_MEDIA_PLAYER = "qs_using_media_player";
-    String QS_USING_COLLAPSED_LANDSCAPE_MEDIA = "qs_using_collapsed_landscape_media";
+@Module(includes = {QSScopeModule.class})
+public  interface QSFragmentModule {
 
-    /**
-     * Provide a context themed using the QS theme
-     */
-    @Provides
-    @QSThemedContext
-    static Context provideThemedContext(@RootView View view) {
-        return view.getContext();
-    }
-
-    /** */
-    @Provides
-    @QSThemedContext
-    static LayoutInflater provideThemedLayoutInflater(@QSThemedContext Context context) {
-        return LayoutInflater.from(context);
-    }
-
-    /** */
     @Provides
     @RootView
-    static View provideRootView(QSFragment qsFragment) {
+    static View provideRootView(QSFragmentLegacy qsFragment) {
         return qsFragment.getView();
     }
 
     /** */
-    @Provides
-    static QSPanel provideQSPanel(@RootView View view) {
-        return view.findViewById(R.id.quick_settings_panel);
-    }
-
-    /** */
-    @Provides
-    static QSContainerImpl providesQSContainerImpl(@RootView View view) {
-        return view.findViewById(R.id.quick_settings_container);
-    }
-
-    /** */
     @Binds
-    QS bindQS(QSFragment qsFragment);
+    QS bindQS(QSFragmentLegacy qsFragment);
 
     /** */
     @Provides
-    static QuickStatusBarHeader providesQuickStatusBarHeader(@RootView View view) {
-        return view.findViewById(R.id.header);
-    }
-
-    /** */
-    @Provides
-    static QuickQSPanel providesQuickQSPanel(QuickStatusBarHeader quickStatusBarHeader) {
-        return quickStatusBarHeader.findViewById(R.id.quick_qs_panel);
-    }
-
-    /** */
-    @Provides
-    static QSFooterView providesQSFooterView(@RootView View view) {
-        return view.findViewById(R.id.qs_footer);
-    }
-
-    /** */
-    @Provides
-    @QSScope
-    static QSFooter providesQSFooter(QSFooterViewController qsFooterViewController) {
-        qsFooterViewController.init();
-        return qsFooterViewController;
-    }
-
-    /** */
-    @Provides
-    @QSScope
-    static QSCustomizer providesQSCutomizer(@RootView View view) {
-        return view.findViewById(R.id.qs_customize);
-    }
-
-    /** */
-    @Provides
-    @Named(QS_USING_MEDIA_PLAYER)
+    @Named(QSScopeModule.QS_USING_MEDIA_PLAYER)
     static boolean providesQSUsingMediaPlayer(Context context) {
         return useQsMediaPlayer(context);
     }
 
+
+
     /** */
     @Provides
-    @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
+    @Named(QSScopeModule.QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
     static boolean providesQSUsingCollapsedLandscapeMedia(Context context) {
         return useCollapsedMediaInLandscape(context.getResources());
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 03de3a0..a65967a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -31,6 +31,7 @@
 import com.android.systemui.qs.external.QSExternalModule;
 import com.android.systemui.qs.pipeline.dagger.QSPipelineModule;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.policy.CastController;
@@ -41,18 +42,18 @@
 import com.android.systemui.statusbar.policy.WalletController;
 import com.android.systemui.util.settings.SecureSettings;
 
-import java.util.Map;
-
-import javax.inject.Named;
-
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.Multibinds;
 
+import java.util.Map;
+
+import javax.inject.Named;
+
 /**
  * Module for QS dependencies
  */
-@Module(subcomponents = {QSFragmentComponent.class},
+@Module(subcomponents = {QSFragmentComponent.class, QSFlexiglassComponent.class},
         includes = {
                 MediaModule.class,
                 QSExternalModule.class,
@@ -68,6 +69,11 @@
     @Multibinds
     Map<String, QSTileImpl<?>> tileMap();
 
+    /** A map of internal QS tile ViewModels. Ensures that this can be injected even if
+     * it is empty */
+    @Multibinds
+    Map<String, QSTileViewModel> tileViewModelMap();
+
     @Provides
     @SysUISingleton
     static AutoTileManager provideAutoTileManager(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSScopeModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSScopeModule.kt
new file mode 100644
index 0000000..e68ec4c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSScopeModule.kt
@@ -0,0 +1,92 @@
+package com.android.systemui.qs.dagger
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import com.android.systemui.dagger.qualifiers.RootView
+import com.android.systemui.qs.QSContainerImpl
+import com.android.systemui.qs.QSFooter
+import com.android.systemui.qs.QSFooterView
+import com.android.systemui.qs.QSFooterViewController
+import com.android.systemui.qs.QSPanel
+import com.android.systemui.qs.QuickQSPanel
+import com.android.systemui.qs.QuickStatusBarHeader
+import com.android.systemui.qs.customize.QSCustomizer
+import com.android.systemui.res.R
+import dagger.Module
+import dagger.Provides
+
+@Module
+interface QSScopeModule {
+    companion object {
+        const val QS_USING_MEDIA_PLAYER = "qs_using_media_player"
+        const val QS_USING_COLLAPSED_LANDSCAPE_MEDIA = "qs_using_collapsed_landscape_media"
+
+        @Provides
+        @QSThemedContext
+        @JvmStatic
+        fun provideThemedContext(@RootView view: View): Context {
+            return view.context
+        }
+
+        /**  */
+        @Provides
+        @QSThemedContext
+        @JvmStatic
+        fun provideThemedLayoutInflater(@QSThemedContext context: Context): LayoutInflater {
+            return LayoutInflater.from(context)
+        }
+
+        /**  */
+        @Provides
+        @JvmStatic
+        fun provideQSPanel(@RootView view: View): QSPanel {
+            return view.requireViewById<QSPanel>(R.id.quick_settings_panel)
+        }
+
+        /**  */
+        @Provides
+        @JvmStatic
+        fun providesQSContainerImpl(@RootView view: View): QSContainerImpl {
+            return view.requireViewById<QSContainerImpl>(R.id.quick_settings_container)
+        }
+
+        /**  */
+        @Provides
+        @JvmStatic
+        fun providesQuickStatusBarHeader(@RootView view: View): QuickStatusBarHeader {
+            return view.requireViewById<QuickStatusBarHeader>(R.id.header)
+        }
+
+        /**  */
+        @Provides
+        @JvmStatic
+        fun providesQuickQSPanel(quickStatusBarHeader: QuickStatusBarHeader): QuickQSPanel {
+            return quickStatusBarHeader.requireViewById<QuickQSPanel>(R.id.quick_qs_panel)
+        }
+
+        /**  */
+        @Provides
+        @JvmStatic
+        fun providesQSFooterView(@RootView view: View): QSFooterView {
+            return view.requireViewById<QSFooterView>(R.id.qs_footer)
+        }
+
+        /**  */
+        @Provides
+        @QSScope
+        @JvmStatic
+        fun providesQSFooter(qsFooterViewController: QSFooterViewController): QSFooter {
+            qsFooterViewController.init()
+            return qsFooterViewController
+        }
+
+        /**  */
+        @Provides
+        @QSScope
+        @JvmStatic
+        fun providesQSCutomizer(@RootView view: View): QSCustomizer {
+            return view.requireViewById<QSCustomizer>(R.id.qs_customize)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index b394a07..8b2c3de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -72,7 +72,7 @@
      * Show the device monitoring dialog, expanded from [expandable] if it's not null.
      *
      * Important: [quickSettingsContext] *must* be the [Context] associated to the
-     * [Quick Settings fragment][com.android.systemui.qs.QSFragment].
+     * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy].
      */
     fun showDeviceMonitoringDialog(quickSettingsContext: Context, expandable: Expandable?)
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index 769cb1f..64fa33c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -23,7 +23,6 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import com.android.settingslib.Utils
-import com.android.systemui.res.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
@@ -34,6 +33,7 @@
 import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
 import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
 import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
+import com.android.systemui.res.R
 import com.android.systemui.util.icuMessageFormat
 import javax.inject.Inject
 import javax.inject.Named
@@ -43,7 +43,6 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
@@ -201,8 +200,8 @@
      * will suspend indefinitely and will need to be cancelled to stop observing.
      *
      * Important: [quickSettingsContext] must be the [Context] associated to the
-     * [Quick Settings fragment][com.android.systemui.qs.QSFragment], and the call to this function
-     * must be cancelled when that fragment is destroyed.
+     * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy], and the call to this
+     * function must be cancelled when that fragment is destroyed.
      */
     suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) {
         footerActionsInteractor.deviceMonitoringDialogRequests.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index a4600fb..21aaa94 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -20,8 +20,12 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.qs.pipeline.data.repository.DefaultTilesQSHostRepository
+import com.android.systemui.qs.pipeline.data.repository.DefaultTilesRepository
 import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
 import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepositoryImpl
+import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredBroadcastRepository
+import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
 import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
@@ -42,6 +46,11 @@
     abstract fun provideTileSpecRepository(impl: TileSpecSettingsRepository): TileSpecRepository
 
     @Binds
+    abstract fun provideDefaultTilesRepository(
+        impl: DefaultTilesQSHostRepository
+    ): DefaultTilesRepository
+
+    @Binds
     abstract fun bindCurrentTilesInteractor(
         impl: CurrentTilesInteractorImpl
     ): CurrentTilesInteractor
@@ -56,6 +65,11 @@
     @ClassKey(QSPipelineCoreStartable::class)
     abstract fun provideCoreStartable(startable: QSPipelineCoreStartable): CoreStartable
 
+    @Binds
+    abstract fun provideQSSettingsRestoredRepository(
+        impl: QSSettingsRestoredBroadcastRepository
+    ): QSSettingsRestoredRepository
+
     companion object {
         /**
          * Provides a logging buffer for all logs related to the new Quick Settings pipeline to log
@@ -67,5 +81,12 @@
         fun provideQSTileListLogBuffer(factory: LogBufferFactory): LogBuffer {
             return factory.create(QSPipelineLogger.TILE_LIST_TAG, maxSize = 700, systrace = false)
         }
+
+        @Provides
+        @SysUISingleton
+        @QSRestoreLog
+        fun providesQSRestoreLogBuffer(factory: LogBufferFactory): LogBuffer {
+            return factory.create(QSPipelineLogger.RESTORE_TAG, maxSize = 50, systrace = false)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSRestoreLog.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSRestoreLog.kt
new file mode 100644
index 0000000..c964929
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSRestoreLog.kt
@@ -0,0 +1,6 @@
+package com.android.systemui.qs.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** A [LogBuffer] for the QS pipeline to track restore of associated settings. */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class QSRestoreLog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreData.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreData.kt
new file mode 100644
index 0000000..d962632
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreData.kt
@@ -0,0 +1,10 @@
+package com.android.systemui.qs.pipeline.data.model
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+/** Data restored from Quick Settings as part of Backup & Restore. */
+data class RestoreData(
+    val restoredTiles: List<TileSpec>,
+    val restoredAutoAddedTiles: Set<TileSpec>,
+    val userId: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt
index 43a16b6..7998dfb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt
@@ -16,28 +16,19 @@
 
 package com.android.systemui.qs.pipeline.data.repository
 
-import android.database.ContentObserver
-import android.provider.Settings
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import android.util.SparseArray
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.model.RestoreData
 import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.util.settings.SecureSettings
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.withContext
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
 
 /** Repository to track what QS tiles have been auto-added */
 interface AutoAddRepository {
 
     /** Flow of tiles that have been auto-added */
-    fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>>
+    suspend fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>>
 
     /** Mark a tile as having been auto-added */
     suspend fun markTileAdded(userId: Int, spec: TileSpec)
@@ -47,89 +38,39 @@
      * multiple times.
      */
     suspend fun unmarkTileAdded(userId: Int, spec: TileSpec)
+
+    suspend fun reconcileRestore(restoreData: RestoreData)
 }
 
 /**
- * Implementation that tracks the auto-added tiles stored in [Settings.Secure.QS_AUTO_ADDED_TILES].
+ * Implementation of [AutoAddRepository] that delegates to an instance of [UserAutoAddRepository]
+ * for each user.
  */
 @SysUISingleton
 class AutoAddSettingRepository
 @Inject
-constructor(
-    private val secureSettings: SecureSettings,
-    @Background private val bgDispatcher: CoroutineDispatcher,
-) : AutoAddRepository {
-    override fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> {
-        return conflatedCallbackFlow {
-                val observer =
-                    object : ContentObserver(null) {
-                        override fun onChange(selfChange: Boolean) {
-                            trySend(Unit)
-                        }
-                    }
+constructor(private val userAutoAddRepositoryFactory: UserAutoAddRepository.Factory) :
+    AutoAddRepository {
 
-                secureSettings.registerContentObserverForUser(SETTING, observer, userId)
+    private val userAutoAddRepositories = SparseArray<UserAutoAddRepository>()
 
-                awaitClose { secureSettings.unregisterContentObserver(observer) }
-            }
-            .onStart { emit(Unit) }
-            .map { secureSettings.getStringForUser(SETTING, userId) ?: "" }
-            .distinctUntilChanged()
-            .map {
-                it.split(DELIMITER).map(TileSpec::create).filter { it !is TileSpec.Invalid }.toSet()
-            }
-            .flowOn(bgDispatcher)
+    override suspend fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> {
+        if (userId !in userAutoAddRepositories) {
+            val repository = userAutoAddRepositoryFactory.create(userId)
+            userAutoAddRepositories.put(userId, repository)
+        }
+        return userAutoAddRepositories.get(userId).autoAdded()
     }
 
     override suspend fun markTileAdded(userId: Int, spec: TileSpec) {
-        if (spec is TileSpec.Invalid) {
-            return
-        }
-        val added = load(userId).toMutableSet()
-        if (added.add(spec)) {
-            store(userId, added)
-        }
+        userAutoAddRepositories.get(userId)?.markTileAdded(spec)
     }
 
     override suspend fun unmarkTileAdded(userId: Int, spec: TileSpec) {
-        if (spec is TileSpec.Invalid) {
-            return
-        }
-        val added = load(userId).toMutableSet()
-        if (added.remove(spec)) {
-            store(userId, added)
-        }
+        userAutoAddRepositories.get(userId)?.unmarkTileAdded(spec)
     }
 
-    private suspend fun store(userId: Int, tiles: Set<TileSpec>) {
-        val toStore =
-            tiles
-                .filter { it !is TileSpec.Invalid }
-                .joinToString(DELIMITER, transform = TileSpec::spec)
-        withContext(bgDispatcher) {
-            secureSettings.putStringForUser(
-                SETTING,
-                toStore,
-                null,
-                false,
-                userId,
-                true,
-            )
-        }
-    }
-
-    private suspend fun load(userId: Int): Set<TileSpec> {
-        return withContext(bgDispatcher) {
-            (secureSettings.getStringForUser(SETTING, userId) ?: "")
-                .split(",")
-                .map(TileSpec::create)
-                .filter { it !is TileSpec.Invalid }
-                .toSet()
-        }
-    }
-
-    companion object {
-        private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES
-        private const val DELIMITER = ","
+    override suspend fun reconcileRestore(restoreData: RestoreData) {
+        userAutoAddRepositories.get(restoreData.userId)?.reconcileRestore(restoreData)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt
new file mode 100644
index 0000000..fe0a69b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt
@@ -0,0 +1,25 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.content.res.Resources
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+interface DefaultTilesRepository {
+    val defaultTiles: List<TileSpec>
+}
+
+@SysUISingleton
+class DefaultTilesQSHostRepository
+@Inject
+constructor(
+    @Main private val resources: Resources,
+) : DefaultTilesRepository {
+    override val defaultTiles: List<TileSpec>
+        get() =
+            QSHost.getDefaultSpecs(resources).map(TileSpec::create).filter {
+                it != TileSpec.Invalid
+            }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt
new file mode 100644
index 0000000..6cee116
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt
@@ -0,0 +1,122 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import android.provider.Settings
+import android.util.Log
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter.toTilesList
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
+
+/** Provides restored data (from Backup and Restore) for Quick Settings pipeline */
+interface QSSettingsRestoredRepository {
+    val restoreData: Flow<RestoreData>
+}
+
+@SysUISingleton
+class QSSettingsRestoredBroadcastRepository
+@Inject
+constructor(
+    broadcastDispatcher: BroadcastDispatcher,
+    logger: QSPipelineLogger,
+    @Application private val scope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : QSSettingsRestoredRepository {
+
+    override val restoreData =
+        flow {
+                val firstIntent = mutableMapOf<Int, Intent>()
+                broadcastDispatcher
+                    .broadcastFlow(INTENT_FILTER, UserHandle.ALL) { intent, receiver ->
+                        intent to receiver.sendingUserId
+                    }
+                    .filter { it.first.isCorrectSetting() }
+                    .collect { (intent, user) ->
+                        if (user !in firstIntent) {
+                            firstIntent[user] = intent
+                        } else {
+                            val firstRestored = firstIntent.remove(user)!!
+                            emit(processIntents(user, firstRestored, intent))
+                        }
+                    }
+            }
+            .catch { Log.e(TAG, "Error parsing broadcast", it) }
+            .flowOn(backgroundDispatcher)
+            .buffer(BUFFER_CAPACITY)
+            .shareIn(scope, SharingStarted.Eagerly)
+            .onEach(logger::logSettingsRestored)
+
+    private fun processIntents(user: Int, intent1: Intent, intent2: Intent): RestoreData {
+        intent1.validateIntent()
+        intent2.validateIntent()
+        val setting1 = intent1.getStringExtra(Intent.EXTRA_SETTING_NAME)
+        val setting2 = intent2.getStringExtra(Intent.EXTRA_SETTING_NAME)
+        val (tiles, autoAdd) =
+            if (setting1 == TILES_SETTING && setting2 == AUTO_ADD_SETTING) {
+                intent1 to intent2
+            } else if (setting1 == AUTO_ADD_SETTING && setting2 == TILES_SETTING) {
+                intent2 to intent1
+            } else {
+                throw IllegalStateException("Wrong intents ($intent1, $intent2)")
+            }
+
+        return RestoreData(
+            (tiles.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) ?: "").toTilesList(),
+            (autoAdd.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) ?: "").toTilesSet(),
+            user,
+        )
+    }
+
+    private companion object {
+        private const val TAG = "QSSettingsRestoredBroadcastRepository"
+        // This capacity is the number of restore data that we will keep buffered in the shared
+        // flow. It is unlikely that at any given time there would be this many restores being
+        // processed by consumers, but just in case that a couple of users are restored at the
+        // same time and they need to be replayed for the consumers of the flow.
+        private const val BUFFER_CAPACITY = 10
+
+        private val INTENT_FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED)
+        private const val TILES_SETTING = Settings.Secure.QS_TILES
+        private const val AUTO_ADD_SETTING = Settings.Secure.QS_AUTO_ADDED_TILES
+        private val requiredExtras =
+            listOf(
+                Intent.EXTRA_SETTING_NAME,
+                Intent.EXTRA_SETTING_PREVIOUS_VALUE,
+                Intent.EXTRA_SETTING_NEW_VALUE,
+            )
+
+        private fun Intent.isCorrectSetting(): Boolean {
+            val setting = getStringExtra(Intent.EXTRA_SETTING_NAME)
+            return setting == TILES_SETTING || setting == AUTO_ADD_SETTING
+        }
+
+        private fun Intent.validateIntent() {
+            requiredExtras.forEach { extra ->
+                if (!hasExtra(extra)) {
+                    throw IllegalStateException("$this doesn't have $extra")
+                }
+            }
+        }
+
+        private fun String.toTilesList() = toTilesList(this)
+
+        private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 47c99f2..00ea0b5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -18,34 +18,19 @@
 
 import android.annotation.UserIdInt
 import android.content.res.Resources
-import android.database.ContentObserver
-import android.provider.Settings
 import android.util.SparseArray
 import com.android.systemui.res.R
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.pipeline.data.model.RestoreData
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import com.android.systemui.retail.data.repository.RetailModeRepository
-import com.android.systemui.util.settings.SecureSettings
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
-import kotlinx.coroutines.withContext
 
 /** Repository that tracks the current tiles. */
 interface TileSpecRepository {
@@ -55,7 +40,7 @@
      *
      * Tiles will never be [TileSpec.Invalid] in the list and it will never be empty.
      */
-    fun tilesSpecs(@UserIdInt userId: Int): Flow<List<TileSpec>>
+    suspend fun tilesSpecs(@UserIdInt userId: Int): Flow<List<TileSpec>>
 
     /**
      * Adds a [tile] for a given [userId] at [position]. Using [POSITION_AT_END] will add the tile
@@ -81,6 +66,8 @@
      */
     suspend fun setTiles(@UserIdInt userId: Int, tiles: List<TileSpec>)
 
+    suspend fun reconcileRestore(restoreData: RestoreData, currentAutoAdded: Set<TileSpec>)
+
     companion object {
         /** Position to indicate the end of the list */
         const val POSITION_AT_END = -1
@@ -88,28 +75,23 @@
 }
 
 /**
- * Implementation of [TileSpecRepository] that persist the values of tiles in
- * [Settings.Secure.QS_TILES].
- *
- * All operations against [Settings] will be performed in a background thread.
+ * Implementation of [TileSpecRepository] that delegates to an instance of [UserTileSpecRepository]
+ * for each user.
  *
  * If the device is in retail mode, the tiles are fixed to the value of
  * [R.string.quick_settings_tiles_retail_mode].
  */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class TileSpecSettingsRepository
 @Inject
 constructor(
-    private val secureSettings: SecureSettings,
     @Main private val resources: Resources,
     private val logger: QSPipelineLogger,
     private val retailModeRepository: RetailModeRepository,
-    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val userTileSpecRepositoryFactory: UserTileSpecRepository.Factory,
 ) : TileSpecRepository {
 
-    private val mutex = Mutex()
-    private val tileSpecsPerUser = SparseArray<List<TileSpec>>()
-
     private val retailModeTiles by lazy {
         resources
             .getString(R.string.quick_settings_tiles_retail_mode)
@@ -118,123 +100,59 @@
             .filter { it !is TileSpec.Invalid }
     }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
-    override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
+    private val userTileRepositories = SparseArray<UserTileSpecRepository>()
+
+    override suspend fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
+        if (userId !in userTileRepositories) {
+            val userTileRepository = userTileSpecRepositoryFactory.create(userId)
+            userTileRepositories.put(userId, userTileRepository)
+        }
+        val realTiles = userTileRepositories.get(userId).tiles()
+
         return retailModeRepository.retailMode.flatMapLatest { inRetailMode ->
             if (inRetailMode) {
                 logger.logUsingRetailTiles()
                 flowOf(retailModeTiles)
             } else {
-                settingsTiles(userId)
+                realTiles
             }
         }
     }
 
-    private fun settingsTiles(userId: Int): Flow<List<TileSpec>> {
-        return conflatedCallbackFlow {
-                val observer =
-                    object : ContentObserver(null) {
-                        override fun onChange(selfChange: Boolean) {
-                            trySend(Unit)
-                        }
-                    }
-
-                secureSettings.registerContentObserverForUser(SETTING, observer, userId)
-
-                awaitClose { secureSettings.unregisterContentObserver(observer) }
-            }
-            .onStart { emit(Unit) }
-            .map { loadTiles(userId) }
-            .onEach { logger.logTilesChangedInSettings(it, userId) }
-            .distinctUntilChanged()
-            .map { parseTileSpecs(it, userId).also { storeTiles(userId, it) } }
-            .distinctUntilChanged()
-            .onEach { mutex.withLock { tileSpecsPerUser.put(userId, it) } }
-            .flowOn(backgroundDispatcher)
-    }
-
-    override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) =
-        mutex.withLock {
-            if (tile == TileSpec.Invalid) {
-                return
-            }
-            val tilesList = tileSpecsPerUser.get(userId, emptyList()).toMutableList()
-            if (tile !in tilesList) {
-                if (position < 0 || position >= tilesList.size) {
-                    tilesList.add(tile)
-                } else {
-                    tilesList.add(position, tile)
-                }
-                storeTiles(userId, tilesList)
-                tileSpecsPerUser.put(userId, tilesList)
-            }
-        }
-
-    override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) =
-        mutex.withLock {
-            if (tiles.all { it == TileSpec.Invalid }) {
-                return
-            }
-            val tilesList = tileSpecsPerUser.get(userId, emptyList()).toMutableList()
-            if (tilesList.removeAll(tiles)) {
-                storeTiles(userId, tilesList.toList())
-                tileSpecsPerUser.put(userId, tilesList)
-            }
-        }
-
-    override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) =
-        mutex.withLock {
-            val filtered = tiles.filter { it != TileSpec.Invalid }
-            if (filtered.isNotEmpty()) {
-                storeTiles(userId, filtered)
-                tileSpecsPerUser.put(userId, tiles)
-            }
-        }
-
-    private suspend fun storeTiles(@UserIdInt forUser: Int, tiles: List<TileSpec>) {
+    override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) {
         if (retailModeRepository.inRetailMode) {
-            // No storing tiles when in retail mode
             return
         }
-        val toStore =
-            tiles
-                .filter { it !is TileSpec.Invalid }
-                .joinToString(DELIMITER, transform = TileSpec::spec)
-        withContext(backgroundDispatcher) {
-            secureSettings.putStringForUser(
-                SETTING,
-                toStore,
-                null,
-                false,
-                forUser,
-                true,
-            )
+        if (tile is TileSpec.Invalid) {
+            return
         }
+        userTileRepositories.get(userId)?.addTile(tile, position)
     }
 
-    private suspend fun loadTiles(userId: Int): String {
-        return withContext(backgroundDispatcher) {
-            secureSettings.getStringForUser(SETTING, userId) ?: ""
+    override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) {
+        if (retailModeRepository.inRetailMode) {
+            return
         }
+        userTileRepositories.get(userId)?.removeTiles(tiles)
     }
 
-    private fun parseTileSpecs(tilesFromSettings: String, user: Int): List<TileSpec> {
-        val fromSettings =
-            tilesFromSettings.split(DELIMITER).map(TileSpec::create).filter {
-                it != TileSpec.Invalid
-            }
-        return if (fromSettings.isNotEmpty()) {
-            fromSettings.also { logger.logParsedTiles(it, false, user) }
-        } else {
-            QSHost.getDefaultSpecs(resources)
-                .map(TileSpec::create)
-                .filter { it != TileSpec.Invalid }
-                .also { logger.logParsedTiles(it, true, user) }
+    override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) {
+        if (retailModeRepository.inRetailMode) {
+            return
         }
+        userTileRepositories.get(userId)?.setTiles(tiles)
+    }
+
+    override suspend fun reconcileRestore(
+        restoreData: RestoreData,
+        currentAutoAdded: Set<TileSpec>
+    ) {
+        userTileRepositories
+            .get(restoreData.userId)
+            ?.reconcileRestore(restoreData, currentAutoAdded)
     }
 
     companion object {
-        private const val SETTING = Settings.Secure.QS_TILES
-        private const val DELIMITER = ","
+        private const val DELIMITER = TilesSettingConverter.DELIMITER
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverter.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverter.kt
new file mode 100644
index 0000000..bb55fcd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverter.kt
@@ -0,0 +1,18 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+object TilesSettingConverter {
+
+    const val DELIMITER = ","
+
+    fun toTilesList(commaSeparatedTiles: String) =
+        commaSeparatedTiles.split(DELIMITER).map(TileSpec::create).filter { it != TileSpec.Invalid }
+
+    fun toTilesSet(commaSeparatedTiles: String) =
+        commaSeparatedTiles
+            .split(DELIMITER)
+            .map(TileSpec::create)
+            .filter { it != TileSpec.Invalid }
+            .toSet()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt
new file mode 100644
index 0000000..d452241
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt
@@ -0,0 +1,186 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.database.ContentObserver
+import android.provider.Settings
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.settings.SecureSettings
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.scan
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Single user version of [AutoAddRepository]. It provides a similar interface as
+ * [AutoAddRepository], but focusing solely on the user it was created for.
+ *
+ * This is the source of truth for that user's tiles, after the user has been started. Persisting
+ * all the changes to [Settings]. Changes in [Settings] that disagree with this repository will be
+ * reverted
+ *
+ * All operations against [Settings] will be performed in a background thread.
+ */
+class UserAutoAddRepository
+@AssistedInject
+constructor(
+    @Assisted private val userId: Int,
+    private val secureSettings: SecureSettings,
+    private val logger: QSPipelineLogger,
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+) {
+
+    private val changeEvents = MutableSharedFlow<ChangeAction>(
+        extraBufferCapacity = CHANGES_BUFFER_SIZE
+    )
+
+    private lateinit var _autoAdded: StateFlow<Set<TileSpec>>
+
+    suspend fun autoAdded(): StateFlow<Set<TileSpec>> {
+        if (!::_autoAdded.isInitialized) {
+            _autoAdded =
+                changeEvents
+                    .scan(load().also { logger.logAutoAddTilesParsed(userId, it) }) {
+                        current,
+                        change ->
+                        change.apply(current).also {
+                            if (change is RestoreTiles) {
+                                logger.logAutoAddTilesRestoredReconciled(userId, it)
+                            }
+                        }
+                    }
+                    .flowOn(bgDispatcher)
+                    .stateIn(applicationScope)
+                    .also { startFlowCollections(it) }
+        }
+        return _autoAdded
+    }
+
+    private fun startFlowCollections(autoAdded: StateFlow<Set<TileSpec>>) {
+        applicationScope.launch(bgDispatcher) {
+            launch { autoAdded.collect { store(it) } }
+            launch {
+                // As Settings is not the source of truth, once we started tracking tiles for a
+                // user, we don't want anyone to change the underlying setting. Therefore, if there
+                // are any changes that don't match with the source of truth (this class), we
+                // overwrite them with the current value.
+                ConflatedCallbackFlow.conflatedCallbackFlow {
+                        val observer =
+                            object : ContentObserver(null) {
+                                override fun onChange(selfChange: Boolean) {
+                                    trySend(Unit)
+                                }
+                            }
+                        secureSettings.registerContentObserverForUser(SETTING, observer, userId)
+                        awaitClose { secureSettings.unregisterContentObserver(observer) }
+                    }
+                    .map { load() }
+                    .flowOn(bgDispatcher)
+                    .collect { setting ->
+                        val current = autoAdded.value
+                        if (setting != current) {
+                            store(current)
+                        }
+                    }
+            }
+        }
+    }
+
+    suspend fun markTileAdded(spec: TileSpec) {
+        if (spec is TileSpec.Invalid) {
+            return
+        }
+        changeEvents.emit(MarkTile(spec))
+    }
+
+    suspend fun unmarkTileAdded(spec: TileSpec) {
+        if (spec is TileSpec.Invalid) {
+            return
+        }
+        changeEvents.emit(UnmarkTile(spec))
+    }
+
+    private suspend fun store(tiles: Set<TileSpec>) {
+        val toStore =
+            tiles
+                .filter { it !is TileSpec.Invalid }
+                .joinToString(DELIMITER, transform = TileSpec::spec)
+        withContext(bgDispatcher) {
+            secureSettings.putStringForUser(
+                SETTING,
+                toStore,
+                null,
+                false,
+                userId,
+                true,
+            )
+        }
+    }
+
+    private suspend fun load(): Set<TileSpec> {
+        return withContext(bgDispatcher) {
+            (secureSettings.getStringForUser(SETTING, userId) ?: "").toTilesSet()
+        }
+    }
+
+    suspend fun reconcileRestore(restoreData: RestoreData) {
+        changeEvents.emit(RestoreTiles(restoreData))
+    }
+
+    private sealed interface ChangeAction {
+        fun apply(currentAutoAdded: Set<TileSpec>): Set<TileSpec>
+    }
+
+    private data class MarkTile(
+        val tileSpec: TileSpec,
+    ) : ChangeAction {
+        override fun apply(currentAutoAdded: Set<TileSpec>): Set<TileSpec> {
+            return currentAutoAdded.toMutableSet().apply { add(tileSpec) }
+        }
+    }
+
+    private data class UnmarkTile(
+        val tileSpec: TileSpec,
+    ) : ChangeAction {
+        override fun apply(currentAutoAdded: Set<TileSpec>): Set<TileSpec> {
+            return currentAutoAdded.toMutableSet().apply { remove(tileSpec) }
+        }
+    }
+
+    private data class RestoreTiles(
+        val restoredData: RestoreData,
+    ) : ChangeAction {
+        override fun apply(currentAutoAdded: Set<TileSpec>): Set<TileSpec> {
+            return currentAutoAdded + restoredData.restoredAutoAddedTiles
+        }
+    }
+
+    companion object {
+        private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES
+        private const val DELIMITER = ","
+        // We want a small buffer in case multiple changes come in at the same time (sometimes
+        // happens in first start. This should be enough to not lose changes.
+        private const val CHANGES_BUFFER_SIZE = 10
+
+        private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(userId: Int): UserAutoAddRepository
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
new file mode 100644
index 0000000..152fd0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
@@ -0,0 +1,252 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.annotation.UserIdInt
+import android.database.ContentObserver
+import android.provider.Settings
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.settings.SecureSettings
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.scan
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Single user version of [TileSpecRepository]. It provides a similar interface as
+ * [TileSpecRepository], but focusing solely on the user it was created for.
+ *
+ * This is the source of truth for that user's tiles, after the user has been started. Persisting
+ * all the changes to [Settings]. Changes in [Settings] that disagree with this repository will be
+ * reverted
+ *
+ * All operations against [Settings] will be performed in a background thread.
+ */
+class UserTileSpecRepository
+@AssistedInject
+constructor(
+    @Assisted private val userId: Int,
+    private val defaultTilesRepository: DefaultTilesRepository,
+    private val secureSettings: SecureSettings,
+    private val logger: QSPipelineLogger,
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+
+    private val defaultTiles: List<TileSpec>
+        get() = defaultTilesRepository.defaultTiles
+
+    private val changeEvents = MutableSharedFlow<ChangeAction>(
+        extraBufferCapacity = CHANGES_BUFFER_SIZE
+    )
+
+    private lateinit var _tiles: StateFlow<List<TileSpec>>
+
+    suspend fun tiles(): Flow<List<TileSpec>> {
+        if (!::_tiles.isInitialized) {
+            _tiles =
+                changeEvents
+                    .scan(loadTilesFromSettingsAndParse(userId)) { current, change ->
+                        change.apply(current).also {
+                            if (current != it) {
+                                if (change is RestoreTiles) {
+                                    logger.logTilesRestoredAndReconciled(current, it, userId)
+                                } else {
+                                    logger.logProcessTileChange(change, it, userId)
+                                }
+                            }
+                        }
+                    }
+                    .flowOn(backgroundDispatcher)
+                    .stateIn(applicationScope)
+                    .also { startFlowCollections(it) }
+        }
+        return _tiles
+    }
+
+    private fun startFlowCollections(tiles: StateFlow<List<TileSpec>>) {
+        applicationScope.launch(backgroundDispatcher) {
+            launch { tiles.collect { storeTiles(userId, it) } }
+            launch {
+                // As Settings is not the source of truth, once we started tracking tiles for a
+                // user, we don't want anyone to change the underlying setting. Therefore, if there
+                // are any changes that don't match with the source of truth (this class), we
+                // overwrite them with the current value.
+                ConflatedCallbackFlow.conflatedCallbackFlow {
+                        val observer =
+                            object : ContentObserver(null) {
+                                override fun onChange(selfChange: Boolean) {
+                                    trySend(Unit)
+                                }
+                            }
+                        secureSettings.registerContentObserverForUser(SETTING, observer, userId)
+                        awaitClose { secureSettings.unregisterContentObserver(observer) }
+                    }
+                    .map { loadTilesFromSettings(userId) }
+                    .flowOn(backgroundDispatcher)
+                    .collect { setting ->
+                        val current = tiles.value
+                        if (setting != current) {
+                            storeTiles(userId, current)
+                        }
+                    }
+            }
+        }
+    }
+
+    private suspend fun storeTiles(@UserIdInt forUser: Int, tiles: List<TileSpec>) {
+        val toStore =
+            tiles
+                .filter { it !is TileSpec.Invalid }
+                .joinToString(DELIMITER, transform = TileSpec::spec)
+        withContext(backgroundDispatcher) {
+            secureSettings.putStringForUser(
+                SETTING,
+                toStore,
+                null,
+                false,
+                forUser,
+                true,
+            )
+        }
+    }
+
+    suspend fun addTile(tile: TileSpec, position: Int = TileSpecRepository.POSITION_AT_END) {
+        if (tile is TileSpec.Invalid) {
+            return
+        }
+        changeEvents.emit(AddTile(tile, position))
+    }
+
+    suspend fun removeTiles(tiles: Collection<TileSpec>) {
+        changeEvents.emit(RemoveTiles(tiles))
+    }
+
+    suspend fun setTiles(tiles: List<TileSpec>) {
+        changeEvents.emit(ChangeTiles(tiles))
+    }
+
+    private fun parseTileSpecs(fromSettings: List<TileSpec>, user: Int): List<TileSpec> {
+        return if (fromSettings.isNotEmpty()) {
+            fromSettings.also { logger.logParsedTiles(it, false, user) }
+        } else {
+            defaultTiles.also { logger.logParsedTiles(it, true, user) }
+        }
+    }
+
+    private suspend fun loadTilesFromSettingsAndParse(userId: Int): List<TileSpec> {
+        return parseTileSpecs(loadTilesFromSettings(userId), userId)
+    }
+
+    private suspend fun loadTilesFromSettings(userId: Int): List<TileSpec> {
+        return withContext(backgroundDispatcher) {
+                secureSettings.getStringForUser(SETTING, userId) ?: ""
+            }
+            .toTilesList()
+    }
+
+    suspend fun reconcileRestore(restoreData: RestoreData, currentAutoAdded: Set<TileSpec>) {
+        changeEvents.emit(RestoreTiles(restoreData, currentAutoAdded))
+    }
+
+    sealed interface ChangeAction {
+        fun apply(currentTiles: List<TileSpec>): List<TileSpec>
+    }
+
+    private data class AddTile(
+        val tileSpec: TileSpec,
+        val position: Int = TileSpecRepository.POSITION_AT_END
+    ) : ChangeAction {
+        override fun apply(currentTiles: List<TileSpec>): List<TileSpec> {
+            val tilesList = currentTiles.toMutableList()
+            if (tileSpec !in tilesList) {
+                if (position < 0 || position >= tilesList.size) {
+                    tilesList.add(tileSpec)
+                } else {
+                    tilesList.add(position, tileSpec)
+                }
+            }
+            return tilesList
+        }
+    }
+
+    private data class RemoveTiles(val tileSpecs: Collection<TileSpec>) : ChangeAction {
+        override fun apply(currentTiles: List<TileSpec>): List<TileSpec> {
+            return currentTiles.toMutableList().apply { removeAll(tileSpecs) }
+        }
+    }
+
+    private data class ChangeTiles(
+        val newTiles: List<TileSpec>,
+    ) : ChangeAction {
+        override fun apply(currentTiles: List<TileSpec>): List<TileSpec> {
+            val new = newTiles.filter { it !is TileSpec.Invalid }
+            return if (new.isNotEmpty()) new else currentTiles
+        }
+    }
+
+    private data class RestoreTiles(
+        val restoreData: RestoreData,
+        val currentAutoAdded: Set<TileSpec>,
+    ) : ChangeAction {
+
+        override fun apply(currentTiles: List<TileSpec>): List<TileSpec> {
+            return reconcileTiles(currentTiles, currentAutoAdded, restoreData)
+        }
+    }
+
+    companion object {
+        private const val SETTING = Settings.Secure.QS_TILES
+        private const val DELIMITER = TilesSettingConverter.DELIMITER
+        // We want a small buffer in case multiple changes come in at the same time (sometimes
+        // happens in first start. This should be enough to not lose changes.
+        private const val CHANGES_BUFFER_SIZE = 10
+
+        private fun String.toTilesList() = TilesSettingConverter.toTilesList(this)
+
+        fun reconcileTiles(
+            currentTiles: List<TileSpec>,
+            currentAutoAdded: Set<TileSpec>,
+            restoreData: RestoreData
+        ): List<TileSpec> {
+            val toRestore = restoreData.restoredTiles.toMutableList()
+            val freshlyAutoAdded =
+                currentAutoAdded.filterNot { it in restoreData.restoredAutoAddedTiles }
+            freshlyAutoAdded
+                .filter { it in currentTiles && it !in restoreData.restoredTiles }
+                .map { it to currentTiles.indexOf(it) }
+                .sortedBy { it.second }
+                .forEachIndexed { iteration, (tile, position) ->
+                    val insertAt = position + iteration
+                    if (insertAt > toRestore.size) {
+                        toRestore.add(tile)
+                    } else {
+                        toRestore.add(insertAt, tile)
+                    }
+                }
+
+            return toRestore
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            userId: Int,
+        ): UserTileSpecRepository
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 5a5e47a..c5512c1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.content.Intent
 import android.os.UserHandle
+import android.util.Log
 import com.android.systemui.Dumpable
 import com.android.systemui.ProtoDumpable
 import com.android.systemui.dagger.SysUISingleton
@@ -40,10 +41,12 @@
 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.qs.tiles.di.NewQSTileFactory
 import com.android.systemui.qs.toProto
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.pairwise
+import dagger.Lazy
 import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -130,6 +133,7 @@
     private val installedTilesComponentRepository: InstalledTilesComponentRepository,
     private val userRepository: UserRepository,
     private val customTileStatePersister: CustomTileStatePersister,
+    private val newQSTileFactory: Lazy<NewQSTileFactory>,
     private val tileFactory: QSFactory,
     private val customTileAddedRepository: CustomTileAddedRepository,
     private val tileLifecycleManagerFactory: TileLifecycleManager.Factory,
@@ -138,7 +142,7 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     @Application private val scope: CoroutineScope,
     private val logger: QSPipelineLogger,
-    featureFlags: QSPipelineFlagsRepository,
+    private val featureFlags: QSPipelineFlagsRepository,
 ) : CurrentTilesInteractor {
 
     private val _currentSpecsAndTiles: MutableStateFlow<List<TileModel>> =
@@ -268,6 +272,7 @@
                             // repository
                             launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
                         }
+                        Log.d("Fabian", "Finished resolving tiles")
                     }
             }
         }
@@ -331,12 +336,19 @@
     }
 
     private suspend fun createTile(spec: TileSpec): QSTile? {
-        val tile = withContext(mainDispatcher) { tileFactory.createTile(spec.spec) }
+        val tile =
+            withContext(mainDispatcher) {
+                if (featureFlags.pipelineTilesEnabled) {
+                    newQSTileFactory.get().createTile(spec.spec)
+                } else {
+                    null
+                }
+                    ?: tileFactory.createTile(spec.spec)
+            }
         if (tile == null) {
             logger.logTileNotFoundInFactory(spec)
             return null
         } else {
-            tile.tileSpec = spec.spec
             return if (!tile.isAvailable) {
                 logger.logTileDestroyed(
                     spec,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt
new file mode 100644
index 0000000..9844903
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt
@@ -0,0 +1,53 @@
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
+import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flatMapConcat
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.launch
+
+/**
+ * Interactor in charge of triggering reconciliation after QS Secure Settings are restored. For a
+ * given user, it will trigger the reconciliations in the correct order to prevent race conditions.
+ *
+ * Currently, the order is:
+ * 1. TileSpecRepository, with the restored data and the current (before restore) auto add tiles
+ * 2. AutoAddRepository
+ *
+ * [start] needs to be called to trigger the collection of [QSSettingsRestoredRepository].
+ */
+@SysUISingleton
+class RestoreReconciliationInteractor
+@Inject
+constructor(
+    private val tileSpecRepository: TileSpecRepository,
+    private val autoAddRepository: AutoAddRepository,
+    private val qsSettingsRestoredRepository: QSSettingsRestoredRepository,
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    fun start() {
+        applicationScope.launch(backgroundDispatcher) {
+            qsSettingsRestoredRepository.restoreData.flatMapConcat { data ->
+                autoAddRepository.autoAddedTiles(data.userId)
+                        .take(1)
+                        .map { tiles -> data to tiles }
+            }.collect { (restoreData, autoAdded) ->
+                tileSpecRepository.reconcileRestore(restoreData, autoAdded)
+                autoAddRepository.reconcileRestore(restoreData)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
index 0743ba0..1539f05 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.RestoreReconciliationInteractor
 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
 import javax.inject.Inject
 
@@ -30,11 +31,13 @@
     private val currentTilesInteractor: CurrentTilesInteractor,
     private val autoAddInteractor: AutoAddInteractor,
     private val featureFlags: QSPipelineFlagsRepository,
+    private val restoreReconciliationInteractor: RestoreReconciliationInteractor,
 ) : CoreStartable {
 
     override fun start() {
         if (featureFlags.pipelineAutoAddEnabled) {
             autoAddInteractor.init(currentTilesInteractor)
+            restoreReconciliationInteractor.start()
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
index 551b0f4..1a71b71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
@@ -1,7 +1,7 @@
 package com.android.systemui.qs.pipeline.shared
 
 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 javax.inject.Inject
 
@@ -10,7 +10,7 @@
 class QSPipelineFlagsRepository
 @Inject
 constructor(
-    private val featureFlags: FeatureFlags,
+    private val featureFlags: FeatureFlagsClassic,
 ) {
 
     /** @see Flags.QS_PIPELINE_NEW_HOST */
@@ -20,4 +20,8 @@
     /** @see Flags.QS_PIPELINE_AUTO_ADD */
     val pipelineAutoAddEnabled: Boolean
         get() = pipelineHostEnabled && featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)
+
+    /** @see Flags.QS_PIPELINE_NEW_TILES */
+    val pipelineTilesEnabled: Boolean
+        get() = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_TILES)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
index 11b5dd7..aed08f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
@@ -31,11 +31,7 @@
 sealed class TileSpec private constructor(open val spec: String) {
 
     /** Represents a spec that couldn't be parsed into a valid type of tile. */
-    object Invalid : TileSpec("") {
-        override fun toString(): String {
-            return "TileSpec.INVALID"
-        }
-    }
+    data object Invalid : TileSpec("")
 
     /** Container for the spec of a tile provided by SystemUI. */
     data class PlatformTileSpec
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index 573cb715..bca86c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.qs.pipeline.shared.logging
 
-import android.annotation.UserIdInt
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.qs.pipeline.dagger.QSAutoAddLog
+import com.android.systemui.qs.pipeline.dagger.QSRestoreLog
 import com.android.systemui.qs.pipeline.dagger.QSTileListLog
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.repository.UserTileSpecRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
 
@@ -34,11 +36,13 @@
 constructor(
     @QSTileListLog private val tileListLogBuffer: LogBuffer,
     @QSAutoAddLog private val tileAutoAddLogBuffer: LogBuffer,
+    @QSRestoreLog private val restoreLogBuffer: LogBuffer,
 ) {
 
     companion object {
         const val TILE_LIST_TAG = "QSTileListLog"
         const val AUTO_ADD_TAG = "QSAutoAddableLog"
+        const val RESTORE_TAG = "QSRestoreLog"
     }
 
     /**
@@ -60,20 +64,37 @@
         )
     }
 
-    /**
-     * Logs when the tiles change in Settings.
-     *
-     * This could be caused by SystemUI, or restore.
-     */
-    fun logTilesChangedInSettings(newTiles: String, @UserIdInt user: Int) {
+    fun logTilesRestoredAndReconciled(
+        currentTiles: List<TileSpec>,
+        reconciledTiles: List<TileSpec>,
+        user: Int,
+    ) {
         tileListLogBuffer.log(
             TILE_LIST_TAG,
-            LogLevel.VERBOSE,
+            LogLevel.DEBUG,
             {
-                str1 = newTiles
+                str1 = currentTiles.toString()
+                str2 = reconciledTiles.toString()
                 int1 = user
             },
-            { "Tiles changed in settings for user $int1: $str1" }
+            { "Tiles restored and reconciled for user: $int1\nWas: $str1\nSet to: $str2" }
+        )
+    }
+
+    fun logProcessTileChange(
+        action: UserTileSpecRepository.ChangeAction,
+        newList: List<TileSpec>,
+        userId: Int,
+    ) {
+        tileListLogBuffer.log(
+            TILE_LIST_TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = action.toString()
+                str2 = newList.toString()
+                int1 = userId
+            },
+            { "Processing $str1 for user $int1\nNew list: $str2" }
         )
     }
 
@@ -139,6 +160,30 @@
         )
     }
 
+    fun logAutoAddTilesParsed(userId: Int, tiles: Set<TileSpec>) {
+        tileAutoAddLogBuffer.log(
+            AUTO_ADD_TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = tiles.toString()
+                int1 = userId
+            },
+            { "Auto add tiles parsed for user $int1: $str1" }
+        )
+    }
+
+    fun logAutoAddTilesRestoredReconciled(userId: Int, tiles: Set<TileSpec>) {
+        tileAutoAddLogBuffer.log(
+            AUTO_ADD_TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = tiles.toString()
+                int1 = userId
+            },
+            { "Auto-add tiles reconciled for user $int1: $str1" }
+        )
+    }
+
     fun logTileAutoAdded(userId: Int, spec: TileSpec, position: Int) {
         tileAutoAddLogBuffer.log(
             AUTO_ADD_TAG,
@@ -164,6 +209,23 @@
         )
     }
 
+    fun logSettingsRestored(restoreData: RestoreData) {
+        restoreLogBuffer.log(
+            RESTORE_TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = restoreData.userId
+                str1 = restoreData.restoredTiles.toString()
+                str2 = restoreData.restoredAutoAddedTiles.toString()
+            },
+            {
+                "Restored settings data for user $int1\n" +
+                    "\tRestored tiles: $str1\n" +
+                    "\tRestored auto added tiles: $str2"
+            }
+        )
+    }
+
     /** Reasons for destroying an existing tile. */
     enum class TileDestroyedReason(val readable: String) {
         TILE_REMOVED("Tile removed from  current set"),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 9c7a734..632aa63 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -70,6 +70,7 @@
         if (tile != null) {
             tile.initialize();
             tile.postStale(); // Tile was just created, must be stale.
+            tile.setTileSpec(tileSpec);
         }
         return tile;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
index e9f907c..dc9e115 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
@@ -1,11 +1,11 @@
 package com.android.systemui.qs.tiles.base.actions
 
 import android.content.Intent
+import android.view.View
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import javax.inject.Inject
 
 /**
@@ -17,9 +17,9 @@
 @Inject
 constructor(private val activityStarter: ActivityStarter) {
 
-    fun handle(userAction: QSTileUserAction, intent: Intent) {
+    fun handle(view: View?, intent: Intent) {
         val animationController: ActivityLaunchAnimator.Controller? =
-            userAction.view?.let {
+            view?.let {
                 ActivityLaunchAnimator.Controller.fromView(
                     it,
                     InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
index c2a75fa..bb4de80 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
@@ -92,7 +92,7 @@
             .stateIn(
                 tileScope,
                 SharingStarted.WhileSubscribed(),
-                false,
+                true,
             )
 
     private var currentLifeState: QSTileLifecycle = QSTileLifecycle.DEAD
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
new file mode 100644
index 0000000..3fedbfc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
@@ -0,0 +1,28 @@
+package com.android.systemui.qs.tiles.di
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter
+import javax.inject.Inject
+import javax.inject.Provider
+
+// TODO(b/http://b/299909989): Rename the factory after rollout
+@SysUISingleton
+class NewQSTileFactory
+@Inject
+constructor(
+    private val adapterFactory: QSTileViewModelAdapter.Factory,
+    private val tileMap:
+        Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>,
+) : QSFactory {
+
+    override fun createTile(tileSpec: String): QSTile? =
+        tileMap[tileSpec]?.let {
+            val tile = it.get()
+            tile.onLifecycle(QSTileLifecycle.ALIVE)
+            adapterFactory.create(tile)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
index a5eaac1..019d3c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
@@ -1,14 +1,13 @@
 package com.android.systemui.qs.tiles.viewmodel
 
-import android.graphics.drawable.Icon
+import androidx.annotation.StringRes
+import com.android.internal.logging.InstanceId
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.qs.pipeline.shared.TileSpec
 
 data class QSTileConfig(
     val tileSpec: TileSpec,
     val tileIcon: Icon,
-    val tileLabel: CharSequence,
-// TODO(b/299908705): Fill necessary params
-/*
-val instanceId: InstanceId,
- */
+    @StringRes val tileLabelRes: Int,
+    val instanceId: InstanceId,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index 53f9edf..dc5c690 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -1,18 +1,96 @@
 package com.android.systemui.qs.tiles.viewmodel
 
-import android.graphics.drawable.Icon
+import android.service.quicksettings.Tile
+import com.android.systemui.common.shared.model.Icon
 
+/**
+ * Represents current a state of the tile to be displayed in on the view. Consider using
+ * [QSTileState.build] for better state creation experience and preset default values for certain
+ * fields.
+ *
+ * // TODO(b/http://b/299909989): Clean up legacy mappings after the transition
+ */
 data class QSTileState(
-    val icon: Icon,
+    val icon: () -> Icon,
     val label: CharSequence,
-// TODO(b/299908705): Fill necessary params
-/*
-   val subtitle: CharSequence = "",
-   val activeState: ActivationState = Active,
-   val enabledState: Enabled = Enabled,
-   val loopIconAnimation: Boolean = false,
-   val secondaryIcon: Icon? = null,
-   val slashState: SlashState? = null,
-   val supportedActions: Collection<UserAction> = listOf(Click), clicks should be a default action
-*/
-)
+    val activationState: ActivationState,
+    val secondaryLabel: CharSequence?,
+    val supportedActions: Set<UserAction>,
+    val contentDescription: CharSequence?,
+    val stateDescription: CharSequence?,
+    val sideViewIcon: SideViewIcon,
+    val enabledState: EnabledState,
+    val expandedAccessibilityClassName: String?,
+) {
+
+    companion object {
+
+        fun build(icon: () -> Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState =
+            Builder(icon, label).apply(build).build()
+
+        fun build(icon: Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState =
+            build({ icon }, label, build)
+    }
+
+    enum class ActivationState(val legacyState: Int) {
+        // An unavailable state indicates that for some reason this tile is not currently available
+        // to the user, and will have no click action. The tile's icon will be tinted differently to
+        // reflect this state.
+        UNAVAILABLE(Tile.STATE_UNAVAILABLE),
+        // This represents a tile that is currently active. (e.g. wifi is connected, bluetooth is
+        // on, cast is casting). This is the default state.
+        ACTIVE(Tile.STATE_ACTIVE),
+        // This represents a tile that is currently in a disabled state but is still interactable. A
+        // disabled state indicates that the tile is not currently active (e.g. wifi disconnected or
+        // bluetooth disabled), but is still interactable by the user to modify this state.
+        INACTIVE(Tile.STATE_INACTIVE),
+    }
+
+    /**
+     * Enabled tile behaves as usual where is disabled one is frozen and inactive in its current
+     * [ActivationState].
+     */
+    enum class EnabledState {
+        ENABLED,
+        DISABLED,
+    }
+
+    enum class UserAction {
+        CLICK,
+        LONG_CLICK,
+    }
+
+    sealed interface SideViewIcon {
+        data class Custom(val icon: Icon) : SideViewIcon
+        data object Chevron : SideViewIcon
+        data object None : SideViewIcon
+    }
+
+    class Builder(
+        var icon: () -> Icon,
+        var label: CharSequence,
+    ) {
+        var activationState: ActivationState = ActivationState.INACTIVE
+        var secondaryLabel: CharSequence? = null
+        var supportedActions: Set<UserAction> = setOf(UserAction.CLICK)
+        var contentDescription: CharSequence? = null
+        var stateDescription: CharSequence? = null
+        var sideViewIcon: SideViewIcon = SideViewIcon.None
+        var enabledState: EnabledState = EnabledState.ENABLED
+        var expandedAccessibilityClassName: String? = null
+
+        fun build(): QSTileState =
+            QSTileState(
+                icon,
+                label,
+                activationState,
+                secondaryLabel,
+                supportedActions,
+                contentDescription,
+                stateDescription,
+                sideViewIcon,
+                enabledState,
+                expandedAccessibilityClassName,
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
index f1f8f01..0b232c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
@@ -1,13 +1,11 @@
 package com.android.systemui.qs.tiles.viewmodel
 
-import android.content.Context
 import android.view.View
 
 sealed interface QSTileUserAction {
 
-    val context: Context
     val view: View?
 
-    class Click(override val context: Context, override val view: View?) : QSTileUserAction
-    class LongClick(override val context: Context, override val view: View?) : QSTileUserAction
+    class Click(override val view: View?) : QSTileUserAction
+    class LongClick(override val view: View?) : QSTileUserAction
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
new file mode 100644
index 0000000..d4bdb77
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -0,0 +1,232 @@
+package com.android.systemui.qs.tiles.viewmodel
+
+import android.content.Context
+import android.util.Log
+import android.view.View
+import androidx.annotation.GuardedBy
+import com.android.internal.logging.InstanceId
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon
+import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.function.Supplier
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.flow.collectIndexed
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+// TODO(b/http://b/299909989): Use QSTileViewModel directly after the rollout
+class QSTileViewModelAdapter
+@AssistedInject
+constructor(
+    private val qsHost: QSHost,
+    @Assisted private val qsTileViewModel: QSTileViewModel,
+) : QSTile {
+
+    private val context
+        get() = qsHost.context
+
+    @GuardedBy("callbacks")
+    private val callbacks: MutableCollection<QSTile.Callback> = mutableSetOf()
+    @GuardedBy("listeningClients")
+    private val listeningClients: MutableCollection<Any> = mutableSetOf()
+
+    // Cancels the jobs when the adapter is no longer alive
+    private val adapterScope = CoroutineScope(SupervisorJob())
+    // Cancels the jobs when clients stop listening
+    private val listeningScope = CoroutineScope(SupervisorJob())
+
+    init {
+        adapterScope.launch {
+            qsTileViewModel.isAvailable.collectIndexed { index, isAvailable ->
+                if (!isAvailable) {
+                    qsHost.removeTile(tileSpec)
+                }
+                // qsTileViewModel.isAvailable flow often starts with isAvailable == true. That's
+                // why we only allow isAvailable == true once and throw an exception afterwards.
+                if (index > 0 && isAvailable) {
+                    // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for additional
+                    // guidance on how to auto add your tile
+                    throw UnsupportedOperationException("Turning on tile is not supported now")
+                }
+            }
+        }
+
+        // QSTileHost doesn't call this when userId is initialized
+        userSwitch(qsHost.userId)
+
+        if (DEBUG) {
+            Log.d(TAG, "Using new tiles for: $tileSpec")
+        }
+    }
+
+    override fun isAvailable(): Boolean = qsTileViewModel.isAvailable.value
+
+    override fun setTileSpec(tileSpec: String?) {
+        throw UnsupportedOperationException("Tile spec is immutable in new tiles")
+    }
+
+    override fun refreshState() {
+        qsTileViewModel.forceUpdate()
+    }
+
+    override fun addCallback(callback: QSTile.Callback?) {
+        callback ?: return
+        synchronized(callbacks) { callbacks.add(callback) }
+    }
+
+    override fun removeCallback(callback: QSTile.Callback?) {
+        callback ?: return
+        synchronized(callbacks) { callbacks.remove(callback) }
+    }
+
+    override fun removeCallbacks() {
+        synchronized(callbacks) { callbacks.clear() }
+    }
+
+    override fun click(view: View?) {
+        if (isActionSupported(QSTileState.UserAction.CLICK)) {
+            qsTileViewModel.onActionPerformed(QSTileUserAction.Click(view))
+        }
+    }
+
+    override fun secondaryClick(view: View?) {
+        if (isActionSupported(QSTileState.UserAction.CLICK)) {
+            qsTileViewModel.onActionPerformed(QSTileUserAction.Click(view))
+        }
+    }
+
+    override fun longClick(view: View?) {
+        if (isActionSupported(QSTileState.UserAction.LONG_CLICK)) {
+            qsTileViewModel.onActionPerformed(QSTileUserAction.LongClick(view))
+        }
+    }
+
+    private fun isActionSupported(action: QSTileState.UserAction): Boolean =
+        qsTileViewModel.currentState?.supportedActions?.contains(action) == true
+
+    override fun userSwitch(currentUser: Int) {
+        qsTileViewModel.onUserIdChanged(currentUser)
+    }
+
+    @Deprecated(
+        "Not needed as {@link com.android.internal.logging.UiEvent} will use #getMetricsSpec",
+        replaceWith = ReplaceWith("getMetricsSpec"),
+    )
+    override fun getMetricsCategory(): Int = 0
+
+    override fun setListening(client: Any?, listening: Boolean) {
+        client ?: return
+        synchronized(listeningClients) {
+            if (listening) {
+                listeningClients.add(client)
+                if (listeningClients.size == 1) {
+                    qsTileViewModel.state
+                        .map { mapState(context, it, qsTileViewModel.config) }
+                        .onEach { legacyState ->
+                            synchronized(callbacks) {
+                                callbacks.forEach { it.onStateChanged(legacyState) }
+                            }
+                        }
+                        .launchIn(listeningScope)
+                }
+            } else {
+                listeningClients.remove(client)
+                if (listeningClients.isEmpty()) {
+                    listeningScope.coroutineContext.cancelChildren()
+                }
+            }
+        }
+    }
+
+    override fun isListening(): Boolean =
+        synchronized(listeningClients) { listeningClients.isNotEmpty() }
+
+    override fun setDetailListening(show: Boolean) {
+        // do nothing like QSTileImpl
+    }
+
+    override fun destroy() {
+        adapterScope.cancel()
+        listeningScope.cancel()
+        qsTileViewModel.onLifecycle(QSTileLifecycle.DEAD)
+    }
+
+    override fun getState(): QSTile.State? =
+        qsTileViewModel.currentState?.let { mapState(context, it, qsTileViewModel.config) }
+
+    override fun getInstanceId(): InstanceId = qsTileViewModel.config.instanceId
+    override fun getTileLabel(): CharSequence =
+        context.getString(qsTileViewModel.config.tileLabelRes)
+    override fun getTileSpec(): String = qsTileViewModel.config.tileSpec.spec
+
+    private companion object {
+
+        const val DEBUG = false
+        const val TAG = "QSTileVMAdapter"
+
+        fun mapState(
+            context: Context,
+            viewModelState: QSTileState,
+            config: QSTileConfig
+        ): QSTile.State =
+            // we have to use QSTile.BooleanState to support different side icons
+            // which are bound to instanceof QSTile.BooleanState in QSTileView.
+            QSTile.BooleanState().apply {
+                spec = config.tileSpec.spec
+                label = viewModelState.label
+                // This value is synthetic and doesn't have any meaning
+                value = false
+
+                secondaryLabel = viewModelState.secondaryLabel
+                handlesLongClick =
+                    viewModelState.supportedActions.contains(QSTileState.UserAction.LONG_CLICK)
+
+                iconSupplier = Supplier {
+                    when (val stateIcon = viewModelState.icon()) {
+                        is Icon.Loaded -> DrawableIcon(stateIcon.drawable)
+                        is Icon.Resource -> ResourceIcon.get(stateIcon.res)
+                    }
+                }
+                state = viewModelState.activationState.legacyState
+
+                contentDescription = viewModelState.contentDescription
+                stateDescription = viewModelState.stateDescription
+
+                disabledByPolicy = viewModelState.enabledState == QSTileState.EnabledState.DISABLED
+                expandedAccessibilityClassName = viewModelState.expandedAccessibilityClassName
+
+                when (viewModelState.sideViewIcon) {
+                    is QSTileState.SideViewIcon.Custom -> {
+                        sideViewCustomDrawable =
+                            when (viewModelState.sideViewIcon.icon) {
+                                is Icon.Loaded -> viewModelState.sideViewIcon.icon.drawable
+                                is Icon.Resource ->
+                                    context.getDrawable(viewModelState.sideViewIcon.icon.res)
+                            }
+                    }
+                    is QSTileState.SideViewIcon.Chevron -> {
+                        forceExpandIcon = true
+                    }
+                    is QSTileState.SideViewIcon.None -> {
+                        forceExpandIcon = false
+                    }
+                }
+            }
+    }
+
+    @AssistedFactory
+    interface Factory {
+
+        fun create(qsTileViewModel: QSTileViewModel): QSTileViewModelAdapter
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 7cdb655..3501b6b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -41,10 +41,10 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.LongRunning;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.media.MediaProjectionCaptureTarget;
+import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget;
+import com.android.systemui.res.R;
 import com.android.systemui.screenrecord.ScreenMediaRecorder.ScreenMediaRecorderListener;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index b80a01212..3aab3bf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -51,7 +51,7 @@
 import android.view.Surface;
 import android.view.WindowManager;
 
-import com.android.systemui.media.MediaProjectionCaptureTarget;
+import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget;
 
 import java.io.Closeable;
 import java.io.File;
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index f0ce8a4..f2e94e9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -18,7 +18,7 @@
 
 import static android.app.Activity.RESULT_OK;
 
-import static com.android.systemui.media.MediaProjectionAppSelectorActivity.KEY_CAPTURE_TARGET;
+import static com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity.KEY_CAPTURE_TARGET;
 import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.INTERNAL;
 import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC;
 import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL;
@@ -41,8 +41,8 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget;
 import com.android.systemui.res.R;
-import com.android.systemui.media.MediaProjectionCaptureTarget;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index b5b7043..a1d5d98 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -32,10 +32,14 @@
 import android.widget.Spinner
 import android.widget.Switch
 import androidx.annotation.LayoutRes
-import com.android.systemui.res.R
-import com.android.systemui.media.MediaProjectionAppSelectorActivity
-import com.android.systemui.media.MediaProjectionCaptureTarget
+import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
+import com.android.systemui.mediaprojection.permission.BaseScreenSharePermissionDialog
+import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
+import com.android.systemui.mediaprojection.permission.SINGLE_APP
+import com.android.systemui.mediaprojection.permission.ScreenShareOption
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserContextProvider
 
 /** Dialog to select screen recording options */
@@ -58,6 +62,7 @@
     private lateinit var tapsView: View
     private lateinit var audioSwitch: Switch
     private lateinit var options: Spinner
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setDialogTitle(R.string.screenrecord_permission_dialog_title)
@@ -177,6 +182,7 @@
             )
         private const val DELAY_MS: Long = 3000
         private const val INTERVAL_MS: Long = 1000
+
         private fun createOptionList(): List<ScreenShareOption> {
             return listOf(
                 ScreenShareOption(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index f08eb14..4b3bd0b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -53,9 +53,6 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
-import android.media.AudioAttributes;
-import android.media.AudioSystem;
-import android.media.MediaPlayer;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Process;
@@ -86,8 +83,6 @@
 import android.window.OnBackInvokedDispatcher;
 import android.window.WindowContext;
 
-import androidx.concurrent.futures.CallbackToFutureAdapter;
-
 import com.android.internal.app.ChooserActivity;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.PhoneWindow;
@@ -108,7 +103,6 @@
 import dagger.assisted.AssistedFactory;
 import dagger.assisted.AssistedInject;
 
-import java.io.File;
 import java.util.List;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
@@ -116,11 +110,11 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
+import javax.inject.Provider;
+
 
 /**
  * Controls the state and flow for screenshots.
@@ -274,7 +268,8 @@
     private final WindowManager mWindowManager;
     private final WindowManager.LayoutParams mWindowLayoutParams;
     private final AccessibilityManager mAccessibilityManager;
-    private final ListenableFuture<MediaPlayer> mCameraSound;
+    @Nullable
+    private final ScreenshotSoundController mScreenshotSoundController;
     private final ScrollCaptureClient mScrollCaptureClient;
     private final PhoneWindow mWindow;
     private final DisplayManager mDisplayManager;
@@ -339,6 +334,7 @@
             UserManager userManager,
             AssistContentRequester assistContentRequester,
             MessageContainerController messageContainerController,
+            Provider<ScreenshotSoundController> screenshotSoundController,
             @Assisted int displayId
     ) {
         mScreenshotSmartActions = screenshotSmartActions;
@@ -387,8 +383,12 @@
         mConfigChanges.applyNewConfig(context.getResources());
         reloadAssets();
 
-        // Setup the Camera shutter sound
-        mCameraSound = loadCameraSound();
+        // Sound is only reproduced from the controller of the default display.
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            mScreenshotSoundController = screenshotSoundController.get();
+        } else {
+            mScreenshotSoundController = null;
+        }
 
         mCopyBroadcastReceiver = new BroadcastReceiver() {
             @Override
@@ -573,17 +573,8 @@
     }
 
     private void releaseMediaPlayer() {
-        // Note that this may block if the sound is still being loaded (very unlikely) but we can't
-        // reliably release in the background because the service is being destroyed.
-        try {
-            MediaPlayer player = mCameraSound.get(1, TimeUnit.SECONDS);
-            if (player != null) {
-                player.release();
-            }
-        } catch (InterruptedException | ExecutionException | TimeoutException e) {
-            mCameraSound.cancel(true);
-            Log.w(TAG, "Error releasing shutter sound", e);
-        }
+        if (mScreenshotSoundController == null) return;
+        mScreenshotSoundController.releaseScreenshotSound();
     }
 
     private void respondToKeyDismissal() {
@@ -889,39 +880,10 @@
         }
     }
 
-    private ListenableFuture<MediaPlayer> loadCameraSound() {
-        // The media player creation is slow and needs on the background thread.
-        return CallbackToFutureAdapter.getFuture((completer) -> {
-            mBgExecutor.execute(() -> {
-                try {
-                    MediaPlayer player = MediaPlayer.create(mContext,
-                            Uri.fromFile(new File(mContext.getResources().getString(
-                                    com.android.internal.R.string.config_cameraShutterSound))),
-                            null,
-                            new AudioAttributes.Builder()
-                                    .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
-                                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-                                    .build(), AudioSystem.newAudioSessionId());
-                    completer.set(player);
-                } catch (IllegalStateException e) {
-                    Log.w(TAG, "Screenshot sound initialization failed", e);
-                    completer.set(null);
-                }
-            });
-            return "ScreenshotController#loadCameraSound";
-        });
-    }
-
-    private void playCameraSound() {
-        mCameraSound.addListener(() -> {
-            try {
-                MediaPlayer player = mCameraSound.get();
-                if (player != null) {
-                    player.start();
-                }
-            } catch (InterruptedException | ExecutionException e) {
-            }
-        }, mBgExecutor);
+    private void playCameraSoundIfNeeded() {
+        if (mScreenshotSoundController == null) return;
+        // the controller is not-null only on the default display controller
+        mScreenshotSoundController.playCameraSound();
     }
 
     /**
@@ -930,7 +892,7 @@
      */
     private void saveScreenshotAndToast(UserHandle owner, Consumer<Uri> finisher) {
         // Play the shutter sound to notify that we've taken a screenshot
-        playCameraSound();
+        playCameraSoundIfNeeded();
 
         saveScreenshotInWorkerThread(
                 owner,
@@ -974,7 +936,7 @@
         }
 
         // Play the shutter sound to notify that we've taken a screenshot
-        playCameraSound();
+        playCameraSoundIfNeeded();
 
         if (DEBUG_ANIM) {
             Log.d(TAG, "starting post-screenshot animation");
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
new file mode 100644
index 0000000..cd0cab5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.systemui.screenshot
+
+import android.media.MediaPlayer
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.TraceUtils.Companion.tracedAsync
+import com.google.errorprone.annotations.CanIgnoreReturnValue
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.withTimeout
+
+/** Controls sound reproduction after a screenshot is taken. */
+interface ScreenshotSoundController {
+    /** Reproduces the camera sound. */
+    @CanIgnoreReturnValue fun playCameraSound(): Deferred<Unit>
+
+    /** Releases the sound. [playCameraSound] behaviour is undefined after this has been called. */
+    @CanIgnoreReturnValue fun releaseScreenshotSound(): Deferred<Unit>
+}
+
+class ScreenshotSoundControllerImpl
+@Inject
+constructor(
+    private val soundProvider: ScreenshotSoundProvider,
+    @Application private val coroutineScope: CoroutineScope,
+    @Background private val bgDispatcher: CoroutineDispatcher
+) : ScreenshotSoundController {
+
+    val player: Deferred<MediaPlayer?> =
+        coroutineScope.tracedAsync("loadCameraSound", bgDispatcher) {
+            try {
+                soundProvider.getScreenshotSound()
+            } catch (e: IllegalStateException) {
+                Log.w(TAG, "Screenshot sound initialization failed", e)
+                null
+            }
+        }
+
+    override fun playCameraSound(): Deferred<Unit> {
+        return coroutineScope.tracedAsync("playCameraSound", bgDispatcher) {
+            player.await()?.start()
+        }
+    }
+    override fun releaseScreenshotSound(): Deferred<Unit> {
+        return coroutineScope.tracedAsync("releaseScreenshotSound", bgDispatcher) {
+            try {
+                withTimeout(1.seconds) { player.await()?.release() }
+            } catch (e: TimeoutCancellationException) {
+                player.cancel()
+                Log.w(TAG, "Error releasing shutter sound", e)
+            }
+        }
+    }
+
+    private companion object {
+        const val TAG = "ScreenshotSoundControllerImpl"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundProvider.kt
new file mode 100644
index 0000000..73151d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundProvider.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.systemui.screenshot
+
+import android.content.Context
+import android.media.AudioAttributes
+import android.media.AudioSystem
+import android.media.MediaPlayer
+import android.net.Uri
+import com.android.internal.R
+import com.android.systemui.dagger.SysUISingleton
+import java.io.File
+import javax.inject.Inject
+
+/** Provides a [MediaPlayer] that reproduces the screenshot sound. */
+interface ScreenshotSoundProvider {
+
+    /**
+     * Creates a new [MediaPlayer] that reproduces the screenshot sound. This should be called from
+     * a background thread, as it might take time.
+     */
+    fun getScreenshotSound(): MediaPlayer
+}
+
+@SysUISingleton
+class ScreenshotSoundProviderImpl
+@Inject
+constructor(
+    private val context: Context,
+) : ScreenshotSoundProvider {
+    override fun getScreenshotSound(): MediaPlayer {
+        return MediaPlayer.create(
+            context,
+            Uri.fromFile(File(context.resources.getString(R.string.config_cameraShutterSound))),
+            /* holder = */ null,
+            AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                .build(),
+            AudioSystem.newAudioSessionId()
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 7d17d4c..3797b8b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -25,6 +25,10 @@
 import com.android.systemui.screenshot.ScreenshotPolicyImpl;
 import com.android.systemui.screenshot.ScreenshotProxyService;
 import com.android.systemui.screenshot.ScreenshotRequestProcessor;
+import com.android.systemui.screenshot.ScreenshotSoundController;
+import com.android.systemui.screenshot.ScreenshotSoundControllerImpl;
+import com.android.systemui.screenshot.ScreenshotSoundProvider;
+import com.android.systemui.screenshot.ScreenshotSoundProviderImpl;
 import com.android.systemui.screenshot.TakeScreenshotService;
 import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
 import com.android.systemui.screenshot.appclips.AppClipsService;
@@ -69,4 +73,12 @@
     @Binds
     abstract ScreenshotRequestProcessor bindScreenshotRequestProcessor(
             RequestProcessor requestProcessor);
+
+    @Binds
+    abstract ScreenshotSoundProvider bindScreenshotSoundProvider(
+            ScreenshotSoundProviderImpl screenshotSoundProviderImpl);
+
+    @Binds
+    abstract ScreenshotSoundController bindScreenshotSoundController(
+            ScreenshotSoundControllerImpl screenshotSoundProviderImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index b715237..6fa592c 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -30,12 +30,13 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.Gefingerpoken;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.haptics.slider.SeekableSliderEventProducer;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.util.ViewController;
@@ -283,6 +284,7 @@
         private final VibratorHelper mVibratorHelper;
         private final SystemClock mSystemClock;
         private final CoroutineDispatcher mMainDispatcher;
+        private final ActivityStarter mActivityStarter;
 
         @Inject
         public Factory(
@@ -291,14 +293,15 @@
                 VibratorHelper vibratorHelper,
                 SystemClock clock,
                 FeatureFlagsClassic featureFlags,
-                @Main CoroutineDispatcher mainDispatcher
-        ) {
+                @Main CoroutineDispatcher mainDispatcher,
+                ActivityStarter activityStarter) {
             mFalsingManager = falsingManager;
             mUiEventLogger = uiEventLogger;
             mFeatureFlags = featureFlags;
             mVibratorHelper = vibratorHelper;
             mSystemClock = clock;
             mMainDispatcher = mainDispatcher;
+            mActivityStarter = activityStarter;
         }
 
         /**
@@ -314,6 +317,8 @@
             int layout = getLayout();
             BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context)
                     .inflate(layout, viewRoot, false);
+            root.setActivityStarter(mActivityStarter);
+
             BrightnessSliderHapticPlugin plugin;
             if (mFeatureFlags.isEnabled(HAPTIC_BRIGHTNESS_SLIDER)) {
                 plugin = new BrightnessSliderHapticPluginImpl(
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index c885492..5ecf07f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -33,6 +33,7 @@
 
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.Gefingerpoken;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.res.R;
 
 /**
@@ -41,6 +42,7 @@
  */
 public class BrightnessSliderView extends FrameLayout {
 
+    private ActivityStarter mActivityStarter;
     @NonNull
     private ToggleSeekBar mSlider;
     private DispatchTouchEventListener mListener;
@@ -57,6 +59,10 @@
         super(context, attrs);
     }
 
+    public void setActivityStarter(@NonNull ActivityStarter activityStarter) {
+        mActivityStarter = activityStarter;
+    }
+
     // Inflated from quick_settings_brightness_dialog
     @Override
     protected void onFinishInflate() {
@@ -65,6 +71,7 @@
 
         mSlider = requireViewById(R.id.slider);
         mSlider.setAccessibilityLabel(getContentDescription().toString());
+        mSlider.setActivityStarter(mActivityStarter);
 
         // Finds the progress drawable. Assumes brightness_progress_drawable.xml
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
index a5a0ae7..6ec10da 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
@@ -23,8 +23,9 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.SeekBar;
 
+import androidx.annotation.NonNull;
+
 import com.android.settingslib.RestrictedLockUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.plugins.ActivityStarter;
 
 public class ToggleSeekBar extends SeekBar {
@@ -32,6 +33,8 @@
 
     private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null;
 
+    private ActivityStarter mActivityStarter;
+
     public ToggleSeekBar(Context context) {
         super(context);
     }
@@ -49,7 +52,7 @@
         if (mEnforcedAdmin != null) {
             Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
                     mContext, mEnforcedAdmin);
-            Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
+            mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
             return true;
         }
         if (!isEnabled()) {
@@ -74,4 +77,8 @@
     public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
         mEnforcedAdmin = admin;
     }
+
+    public void setActivityStarter(@NonNull ActivityStarter activityStarter) {
+        mActivityStarter = activityStarter;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index e8be40e..9b74ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -791,7 +791,8 @@
     /** update Qs height state */
     public void setExpansionHeight(float height) {
         // TODO(b/277909752): remove below log when bug is fixed
-        if (mSplitShadeEnabled && mShadeExpandedFraction == 1.0f && height == 0) {
+        if (mSplitShadeEnabled && mShadeExpandedFraction == 1.0f && height == 0
+                && mBarState == SHADE) {
             Log.wtf(TAG,
                     "setting QS height to 0 in split shade while shade is open(ing). "
                             + "Value of mExpandImmediate = " + mExpandImmediate);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 251cc16..ac8333a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -129,6 +129,9 @@
         combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) }
             .stateIn(scope, SharingStarted.Eagerly, 0f)
 
+    /** Whether either the shade or QS is fully expanded. */
+    val isAnyFullyExpanded: Flow<Boolean> = anyExpansion.map { it >= 1f }.distinctUntilChanged()
+
     /** Whether either the shade or QS is expanding from a fully collapsed state. */
     val isAnyExpanding: Flow<Boolean> =
         anyExpansion
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 670fb12..93bb435 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -172,6 +172,7 @@
     private static final int MSG_LOCK_TASK_MODE_CHANGED = 75 << MSG_SHIFT;
     private static final int MSG_CONFIRM_IMMERSIVE_PROMPT = 77 << MSG_SHIFT;
     private static final int MSG_IMMERSIVE_CHANGED = 78 << MSG_SHIFT;
+    private static final int MSG_SET_QS_TILES = 79 << MSG_SHIFT;
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
     public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1;
@@ -301,6 +302,8 @@
 
         default void addQsTile(ComponentName tile) { }
         default void remQsTile(ComponentName tile) { }
+
+        default void setQsTiles(String[] tiles) {}
         default void clickTile(ComponentName tile) { }
 
         default void handleSystemKey(KeyEvent arg1) { }
@@ -903,6 +906,13 @@
     }
 
     @Override
+    public void setQsTiles(String[] tiles) {
+        synchronized (mLock) {
+            mHandler.obtainMessage(MSG_SET_QS_TILES, tiles).sendToTarget();
+        }
+    }
+
+    @Override
     public void clickQsTile(ComponentName tile) {
         synchronized (mLock) {
             mHandler.obtainMessage(MSG_CLICK_QS_TILE, tile).sendToTarget();
@@ -1533,6 +1543,11 @@
                         mCallbacks.get(i).remQsTile((ComponentName) msg.obj);
                     }
                     break;
+                case MSG_SET_QS_TILES:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).setQsTiles((String[]) msg.obj);
+                    }
+                    break;
                 case MSG_CLICK_QS_TILE:
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).clickTile((ComponentName) msg.obj);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 8baab25..f616b91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -244,12 +244,16 @@
             }
             final float scaledImageWidth = drawableWidth * scaleToFitIconView;
             final float scaledImageHeight = drawableHeight * scaleToFitIconView;
-            // if the scaled image size <= mOriginalStatusBarIconSize, we don't need to enlarge it
             scaleToOriginalDrawingSize = Math.min(
                     (float) mOriginalStatusBarIconSize / scaledImageWidth,
                     (float) mOriginalStatusBarIconSize / scaledImageHeight);
             if (scaleToOriginalDrawingSize > 1.0f) {
-                scaleToOriginalDrawingSize = 1.0f;
+                // per b/296026932, if the scaled image size <= mOriginalStatusBarIconSize, we need
+                // to scale up the scaled image to fit in mOriginalStatusBarIconSize. But if both
+                // the raw drawable intrinsic width/height are less than mOriginalStatusBarIconSize,
+                // then we just scale up the scaled image back to the raw drawable size.
+                scaleToOriginalDrawingSize = Math.min(
+                        scaleToOriginalDrawingSize, 1f / scaleToFitIconView);
             }
         }
         iconScale = scaleToOriginalDrawingSize;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/CallLayoutSetDataAsyncFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/CallLayoutSetDataAsyncFactory.kt
new file mode 100644
index 0000000..4deebdb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/CallLayoutSetDataAsyncFactory.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import com.android.internal.widget.CallLayout
+import javax.inject.Inject
+
+class CallLayoutSetDataAsyncFactory @Inject constructor() : NotifRemoteViewsFactory {
+    override fun instantiate(
+        row: ExpandableNotificationRow,
+        @NotificationRowContentBinder.InflationFlag layoutType: Int,
+        parent: View?,
+        name: String,
+        context: Context,
+        attrs: AttributeSet
+    ): View? =
+        if (name == CallLayout::class.java.name)
+            CallLayout(context, attrs).apply { setSetDataAsyncEnabled(true) }
+        else null
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index 0239afc..3a59978 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -61,7 +61,8 @@
     static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
             FeatureFlags featureFlags,
             PrecomputedTextViewFactory precomputedTextViewFactory,
-            BigPictureLayoutInflaterFactory bigPictureLayoutInflaterFactory
+            BigPictureLayoutInflaterFactory bigPictureLayoutInflaterFactory,
+            CallLayoutSetDataAsyncFactory callLayoutSetDataAsyncFactory
     ) {
         final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
         if (featureFlags.isEnabled(Flags.PRECOMPUTED_TEXT)) {
@@ -70,6 +71,9 @@
         if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
             replacementFactories.add(bigPictureLayoutInflaterFactory);
         }
+        if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
+            replacementFactories.add(callLayoutSetDataAsyncFactory);
+        }
         return replacementFactories;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
index 875a409..91b12cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
@@ -20,6 +20,9 @@
 
 import static com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.DEFAULT_HEADER_VISIBLE_AMOUNT;
 
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -34,8 +37,7 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
-import androidx.annotation.Nullable;
-
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.internal.widget.NotificationActionListLayout;
 import com.android.systemui.Dependency;
@@ -49,6 +51,8 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.HybridNotificationView;
 
+import java.util.function.Consumer;
+
 /**
  * Wraps a notification view inflated from a template.
  */
@@ -66,9 +70,13 @@
 
     private int mContentHeight;
     private int mMinHeightHint;
+    @Nullable
     private NotificationActionListLayout mActions;
-    private ArraySet<PendingIntent> mCancelledPendingIntents = new ArraySet<>();
-    private UiOffloadThread mUiOffloadThread;
+    // Holds list of pending intents that have been cancelled by now - we only keep hash codes
+    // to avoid holding full binder proxies for intents that may have been removed by now.
+    @NonNull
+    @VisibleForTesting
+    final ArraySet<Integer> mCancelledPendingIntents = new ArraySet<>();
     private View mRemoteInputHistory;
     private boolean mCanHideHeader;
     private float mHeaderTranslation;
@@ -147,6 +155,7 @@
                 com.android.internal.R.dimen.notification_content_margin_top);
     }
 
+    @MainThread
     private void resolveTemplateViews(StatusBarNotification sbn) {
         mRightIcon = mView.findViewById(com.android.internal.R.id.right_icon);
         if (mRightIcon != null) {
@@ -195,34 +204,57 @@
         return getLargeIcon(n);
     }
 
+    @MainThread
     private void updatePendingIntentCancellations() {
         if (mActions != null) {
             int numActions = mActions.getChildCount();
+            final ArraySet<Integer> currentlyActivePendingIntents = new ArraySet<>(numActions);
             for (int i = 0; i < numActions; i++) {
                 Button action = (Button) mActions.getChildAt(i);
-                performOnPendingIntentCancellation(action, () -> {
-                    if (action.isEnabled()) {
-                        action.setEnabled(false);
-                        // The visual appearance doesn't look disabled enough yet, let's add the
-                        // alpha as well. Since Alpha doesn't play nicely right now with the
-                        // transformation, we rather blend it manually with the background color.
-                        ColorStateList textColors = action.getTextColors();
-                        int[] colors = textColors.getColors();
-                        int[] newColors = new int[colors.length];
-                        float disabledAlpha = mView.getResources().getFloat(
-                                com.android.internal.R.dimen.notification_action_disabled_alpha);
-                        for (int j = 0; j < colors.length; j++) {
-                            int color = colors[j];
-                            color = blendColorWithBackground(color, disabledAlpha);
-                            newColors[j] = color;
-                        }
-                        ColorStateList newColorStateList = new ColorStateList(
-                                textColors.getStates(), newColors);
-                        action.setTextColor(newColorStateList);
+                PendingIntent pendingIntent = getPendingIntentForAction(action);
+                // Check if passed intent has already been cancelled in this class and immediately
+                // disable the action to avoid temporary race with enable/disable.
+                if (pendingIntent != null) {
+                    int pendingIntentHashCode = getHashCodeForPendingIntent(pendingIntent);
+                    currentlyActivePendingIntents.add(pendingIntentHashCode);
+                    if (mCancelledPendingIntents.contains(pendingIntentHashCode)) {
+                        disableActionView(action);
                     }
-                });
+                }
+                updatePendingIntentCancellationListener(action, pendingIntent);
+            }
+
+            // This cleanup ensures that the size of this set doesn't grow into unreasonable sizes.
+            // There are scenarios where applications updated notifications with different
+            // PendingIntents which could cause this Set to grow to 1000+ elements.
+            mCancelledPendingIntents.retainAll(currentlyActivePendingIntents);
+        }
+    }
+
+    @MainThread
+    private void updatePendingIntentCancellationListener(Button action,
+            @Nullable PendingIntent pendingIntent) {
+        ActionPendingIntentCancellationHandler cancellationHandler = null;
+        if (pendingIntent != null) {
+            // Attach listeners to handle intent cancellation to this view.
+            cancellationHandler = new ActionPendingIntentCancellationHandler(pendingIntent, action,
+                    this::disableActionViewWithIntent);
+            action.addOnAttachStateChangeListener(cancellationHandler);
+            // Immediately fire the event if the view is already attached to register
+            // pending intent cancellation listener.
+            if (action.isAttachedToWindow()) {
+                cancellationHandler.onViewAttachedToWindow(action);
             }
         }
+
+        // If the view has an old attached listener, remove it to avoid leaking intents.
+        ActionPendingIntentCancellationHandler previousHandler =
+                (ActionPendingIntentCancellationHandler) action.getTag(
+                        R.id.pending_intent_listener_tag);
+        if (previousHandler != null) {
+            previousHandler.remove();
+        }
+        action.setTag(R.id.pending_intent_listener_tag, cancellationHandler);
     }
 
     private int blendColorWithBackground(int color, float alpha) {
@@ -231,42 +263,6 @@
                 Color.red(color), Color.green(color), Color.blue(color)), resolveBackgroundColor());
     }
 
-    private void performOnPendingIntentCancellation(View view, Runnable cancellationRunnable) {
-        PendingIntent pendingIntent = (PendingIntent) view.getTag(
-                com.android.internal.R.id.pending_intent_tag);
-        if (pendingIntent == null) {
-            return;
-        }
-        if (mCancelledPendingIntents.contains(pendingIntent)) {
-            cancellationRunnable.run();
-        } else {
-            PendingIntent.CancelListener listener = (PendingIntent intent) -> {
-                mView.post(() -> {
-                    mCancelledPendingIntents.add(pendingIntent);
-                    cancellationRunnable.run();
-                });
-            };
-            if (mUiOffloadThread == null) {
-                mUiOffloadThread = Dependency.get(UiOffloadThread.class);
-            }
-            if (view.isAttachedToWindow()) {
-                mUiOffloadThread.execute(() -> pendingIntent.registerCancelListener(listener));
-            }
-            view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
-                @Override
-                public void onViewAttachedToWindow(View v) {
-                    mUiOffloadThread.execute(() -> pendingIntent.registerCancelListener(listener));
-                }
-
-                @Override
-                public void onViewDetachedFromWindow(View v) {
-                    mUiOffloadThread.execute(
-                            () -> pendingIntent.unregisterCancelListener(listener));
-                }
-            });
-        }
-    }
-
     @Override
     public void onContentUpdated(ExpandableNotificationRow row) {
         // Reinspect the notification. Before the super call, because the super call also updates
@@ -364,4 +360,141 @@
         }
         return extra + super.getExtraMeasureHeight();
     }
+
+    /**
+     * This finds Action view with a given intent and disables it.
+     * With maximum of 3 views, this is sufficiently fast to iterate on main thread every time.
+     */
+    @MainThread
+    private void disableActionViewWithIntent(PendingIntent intent) {
+        mCancelledPendingIntents.add(getHashCodeForPendingIntent(intent));
+        if (mActions != null) {
+            int numActions = mActions.getChildCount();
+            for (int i = 0; i < numActions; i++) {
+                Button action = (Button) mActions.getChildAt(i);
+                PendingIntent pendingIntent = getPendingIntentForAction(action);
+                if (intent.equals(pendingIntent)) {
+                    disableActionView(action);
+                }
+            }
+        }
+    }
+
+    /**
+     * Disables Action view when, e.g., its PendingIntent is disabled.
+     */
+    @MainThread
+    private void disableActionView(Button action) {
+        if (action.isEnabled()) {
+            action.setEnabled(false);
+            // The visual appearance doesn't look disabled enough yet, let's add the
+            // alpha as well. Since Alpha doesn't play nicely right now with the
+            // transformation, we rather blend it manually with the background color.
+            ColorStateList textColors = action.getTextColors();
+            int[] colors = textColors.getColors();
+            int[] newColors = new int[colors.length];
+            float disabledAlpha = mView.getResources().getFloat(
+                    com.android.internal.R.dimen.notification_action_disabled_alpha);
+            for (int j = 0; j < colors.length; j++) {
+                int color = colors[j];
+                color = blendColorWithBackground(color, disabledAlpha);
+                newColors[j] = color;
+            }
+            ColorStateList newColorStateList = new ColorStateList(
+                    textColors.getStates(), newColors);
+            action.setTextColor(newColorStateList);
+        }
+    }
+
+    /**
+     * Returns the hashcode of underlying target of PendingIntent. We can get multiple
+     * Java PendingIntent wrapper objects pointing to the same cancelled PI in system_server.
+     * This makes sure we treat them equally.
+     */
+    private static int getHashCodeForPendingIntent(PendingIntent pendingIntent) {
+        return System.identityHashCode(pendingIntent.getTarget().asBinder());
+    }
+
+    /**
+     * Returns PendingIntent contained in the action tag. May be null.
+     */
+    @Nullable
+    private static PendingIntent getPendingIntentForAction(View action) {
+        return (PendingIntent) action.getTag(com.android.internal.R.id.pending_intent_tag);
+    }
+
+    /**
+     * Registers listeners for pending intent cancellation when Action views are attached
+     * to window.
+     * It calls onCancelPendingIntentForActionView when a PendingIntent is cancelled.
+     */
+    @VisibleForTesting
+    static final class ActionPendingIntentCancellationHandler
+            implements View.OnAttachStateChangeListener {
+
+        @Nullable
+        private static UiOffloadThread sUiOffloadThread = null;
+
+        @NonNull
+        private static UiOffloadThread getUiOffloadThread() {
+            if (sUiOffloadThread == null) {
+                sUiOffloadThread = Dependency.get(UiOffloadThread.class);
+            }
+            return sUiOffloadThread;
+        }
+
+        private final View mView;
+        private final Consumer<PendingIntent> mOnCancelledCallback;
+
+        private final PendingIntent mPendingIntent;
+
+        ActionPendingIntentCancellationHandler(PendingIntent pendingIntent, View actionView,
+                Consumer<PendingIntent> onCancelled) {
+            this.mPendingIntent = pendingIntent;
+            this.mView = actionView;
+            this.mOnCancelledCallback = onCancelled;
+        }
+
+        private final PendingIntent.CancelListener mCancelListener =
+                new PendingIntent.CancelListener() {
+            @Override
+            public void onCanceled(PendingIntent pendingIntent) {
+                mView.post(() -> {
+                    mOnCancelledCallback.accept(pendingIntent);
+                    // We don't need this listener anymore once the intent was cancelled.
+                    remove();
+                });
+            }
+        };
+
+        @MainThread
+        @Override
+        public void onViewAttachedToWindow(View view) {
+            // This is safe to call multiple times with the same listener instance.
+            getUiOffloadThread().execute(() -> {
+                mPendingIntent.registerCancelListener(mCancelListener);
+            });
+        }
+
+        @MainThread
+        @Override
+        public void onViewDetachedFromWindow(View view) {
+            // This is safe to call multiple times with the same listener instance.
+            getUiOffloadThread().execute(() ->
+                    mPendingIntent.unregisterCancelListener(mCancelListener));
+        }
+
+        /**
+         * Removes this listener from callbacks and releases the held PendingIntent.
+         */
+        @MainThread
+        public void remove() {
+            mView.removeOnAttachStateChangeListener(this);
+            if (mView.getTag(R.id.pending_intent_listener_tag) == this) {
+                mView.setTag(R.id.pending_intent_listener_tag, null);
+            }
+            getUiOffloadThread().execute(() ->
+                    mPendingIntent.unregisterCancelListener(mCancelListener));
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 485ab32..f7ff39c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -75,6 +75,7 @@
 
 import dagger.Lazy;
 
+import java.util.Arrays;
 import java.util.Optional;
 
 import javax.inject.Inject;
@@ -201,6 +202,11 @@
     }
 
     @Override
+    public void setQsTiles(String[] tiles) {
+        mQSHost.changeTilesByUser(mQSHost.getSpecs(), Arrays.stream(tiles).toList());
+    }
+
+    @Override
     public void clickTile(ComponentName tile) {
         // Can't inject this because it changes with the QS fragment
         QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 6f69ea81..05beded 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -24,9 +24,11 @@
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
+
 import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
 import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
 import static androidx.lifecycle.Lifecycle.State.RESUMED;
+
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
@@ -121,7 +123,6 @@
 import com.android.systemui.EventLogTags;
 import com.android.systemui.InitController;
 import com.android.systemui.Prefs;
-import com.android.systemui.res.R;
 import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.assist.AssistManager;
@@ -165,8 +166,9 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
-import com.android.systemui.qs.QSFragment;
+import com.android.systemui.qs.QSFragmentLegacy;
 import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.settings.UserTracker;
@@ -244,8 +246,6 @@
 import com.android.wm.shell.startingsurface.SplashscreenContentDrawer;
 import com.android.wm.shell.startingsurface.StartingSurface;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.List;
@@ -257,6 +257,8 @@
 import javax.inject.Named;
 import javax.inject.Provider;
 
+import dagger.Lazy;
+
 /**
  * A class handling initialization and coordination between some of the key central surfaces in
  * System UI: The notification shade, the keyguard (lockscreen), and the status bar.
@@ -1346,9 +1348,10 @@
                     });
             fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
                 QS qs = (QS) f;
-                if (qs instanceof QSFragment) {
-                    mQSPanelController = ((QSFragment) qs).getQSPanelController();
-                    ((QSFragment) qs).setBrightnessMirrorController(mBrightnessMirrorController);
+                if (qs instanceof QSFragmentLegacy) {
+                    QSFragmentLegacy qsFragment = (QSFragmentLegacy) qs;
+                    mQSPanelController = qsFragment.getQSPanelController();
+                    qsFragment.setBrightnessMirrorController(mBrightnessMirrorController);
                 }
             });
         }
@@ -1502,7 +1505,7 @@
     protected QS createDefaultQSFragment() {
         return mFragmentService
                 .getFragmentHostManager(getNotificationShadeWindowView())
-                .create(QSFragment.class);
+                .create(QSFragmentLegacy.class);
     }
 
     private void setUpPresenter() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index b53939e..a27e67b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -112,6 +112,9 @@
         mDisplayCutout = null;
     }
 
+    // Per b/300629388, we let the PhoneStatusBarView detect onConfigurationChanged to
+    // updateResources, instead of letting the PhoneStatusBarViewController detect onConfigChanged
+    // then notify PhoneStatusBarView.
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index e1096e2..f9702dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -16,7 +16,6 @@
 package com.android.systemui.statusbar.phone
 
 import android.app.StatusBarManager.WINDOW_STATUS_BAR
-import android.content.res.Configuration
 import android.graphics.Point
 import android.util.Log
 import android.view.MotionEvent
@@ -72,10 +71,6 @@
 
     private val configurationListener =
         object : ConfigurationController.ConfigurationListener {
-            override fun onConfigChanged(newConfig: Configuration?) {
-                mView.updateResources()
-            }
-
             override fun onDensityOrFontScaleChanged() {
                 mView.onDensityOrFontScaleChanged()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 727d649..3c6d90d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -135,6 +135,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.AlphaTintDrawableWrapper;
 import com.android.systemui.util.RoundedCornerProgressDrawable;
+import com.android.systemui.util.settings.SecureSettings;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -304,6 +305,8 @@
     private @DevicePostureController.DevicePostureInt int mDevicePosture;
     private int mOrientation;
     private final FeatureFlags mFeatureFlags;
+    private final SecureSettings mSecureSettings;
+    private int mDialogTimeoutMillis;
 
     public VolumeDialogImpl(
             Context context,
@@ -320,7 +323,8 @@
             DevicePostureController devicePostureController,
             Looper looper,
             DumpManager dumpManager,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            SecureSettings secureSettings) {
         mFeatureFlags = featureFlags;
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
@@ -351,6 +355,8 @@
         mUseBackgroundBlur =
             mContext.getResources().getBoolean(R.bool.config_volumeDialogUseBackgroundBlur);
         mInteractionJankMonitor = interactionJankMonitor;
+        mSecureSettings = secureSettings;
+        mDialogTimeoutMillis = DIALOG_TIMEOUT_MILLIS;
 
         dumpManager.registerDumpable("VolumeDialogImpl", this);
 
@@ -515,6 +521,8 @@
         mDialog.setContentView(R.layout.volume_dialog);
         mDialogView = mDialog.findViewById(R.id.volume_dialog);
         mDialogView.setAlpha(0);
+        mDialogTimeoutMillis = mSecureSettings.getInt(Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT,
+                DIALOG_TIMEOUT_MILLIS);
         mDialog.setCanceledOnTouchOutside(true);
         mDialog.setOnShowListener(dialog -> {
             mDialogView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
@@ -527,7 +535,7 @@
                     .alpha(1)
                     .translationX(0)
                     .setDuration(mDialogShowAnimationDurationMs)
-                    .setListener(getJankListener(getDialogView(), TYPE_SHOW, DIALOG_TIMEOUT_MILLIS))
+                    .setListener(getJankListener(getDialogView(), TYPE_SHOW, mDialogTimeoutMillis))
                     .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
                     .withEndAction(() -> {
                         if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) {
@@ -1514,7 +1522,7 @@
                     AccessibilityManager.FLAG_CONTENT_TEXT
                             | AccessibilityManager.FLAG_CONTENT_CONTROLS);
         }
-        return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS,
+        return mAccessibilityMgr.getRecommendedTimeoutMillis(mDialogTimeoutMillis,
                 AccessibilityManager.FLAG_CONTENT_CONTROLS);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index cc9f3e1..624691b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -31,6 +31,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.volume.CsdWarningDialog;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.volume.VolumeDialogComponent;
@@ -63,7 +64,8 @@
             CsdWarningDialog.Factory csdFactory,
             DevicePostureController devicePostureController,
             DumpManager dumpManager,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            SecureSettings secureSettings) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
                 volumeDialogController,
@@ -79,7 +81,8 @@
                 devicePostureController,
                 Looper.getMainLooper(),
                 dumpManager,
-                featureFlags);
+                featureFlags,
+                secureSettings);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
         impl.setSilentMode(false);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index ba3dbf0..484b119 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -20,8 +20,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.animation.LayoutTransition;
 import android.view.View;
 import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.keyguard.logging.KeyguardLogger;
@@ -44,6 +46,7 @@
 public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase {
 
     @Mock protected KeyguardStatusView mKeyguardStatusView;
+
     @Mock protected KeyguardSliceViewController mKeyguardSliceViewController;
     @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController;
     @Mock protected KeyguardStateController mKeyguardStateController;
@@ -61,6 +64,10 @@
 
     protected KeyguardStatusViewController mController;
 
+    @Mock protected KeyguardClockSwitch mKeyguardClockSwitch;
+    @Mock protected FrameLayout mMediaHostContainer;
+    @Mock protected LayoutTransition mMediaLayoutTransition;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -93,6 +100,8 @@
                 };
 
         when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
+
+        when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
     }
 
     protected void givenViewAttached() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index b8b0198..e4e2b0a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -18,14 +18,18 @@
 
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.animation.LayoutTransition;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.View;
 
+import com.android.systemui.res.R;
 import com.android.systemui.plugins.ClockConfig;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
@@ -124,4 +128,112 @@
         mController.onDestroy();
         verify(mDumpManager, times(1)).unregisterDumpable(eq(mController.getInstanceName()));
     }
+
+    @Test
+    public void onInit_addsOnLayoutChangeListenerToClockSwitch() {
+        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
+                mMediaHostContainer);
+
+        mController.onInit();
+
+        ArgumentCaptor<View.OnLayoutChangeListener> captor =
+                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+        verify(mKeyguardClockSwitch).addOnLayoutChangeListener(captor.capture());
+    }
+
+    @Test
+    public void onInit_addsOnLayoutChangeListenerToMediaHostContainer() {
+        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
+                mMediaHostContainer);
+
+        mController.onInit();
+
+        ArgumentCaptor<View.OnLayoutChangeListener> captor =
+                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+        verify(mMediaHostContainer).addOnLayoutChangeListener(captor.capture());
+    }
+
+    @Test
+    public void clockSwitchHeightChanged_mediaChangingLayoutTransitionEnabled() {
+        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
+                mMediaHostContainer);
+
+        mController.onInit();
+
+        ArgumentCaptor<View.OnLayoutChangeListener> captor =
+                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+        verify(mKeyguardClockSwitch).addOnLayoutChangeListener(captor.capture());
+
+        // Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`.
+        // Below here is the actual test.
+
+        View.OnLayoutChangeListener listener = captor.getValue();
+
+        mController.setSplitShadeEnabled(true);
+        when(mKeyguardClockSwitch.getSplitShadeCentered()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true);
+        when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE);
+        when(mMediaHostContainer.getHeight()).thenReturn(200);
+        when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition);
+
+        when(mKeyguardClockSwitch.getHeight()).thenReturn(0);
+        listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */
+                0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
+                0, /* oldBottom = */ 200);
+        verify(mMediaLayoutTransition).enableTransitionType(LayoutTransition.CHANGING);
+    }
+
+    @Test
+    public void clockSwitchHeightNotChanged_mediaChangingLayoutTransitionNotEnabled() {
+        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
+                mMediaHostContainer);
+
+        mController.onInit();
+
+        ArgumentCaptor<View.OnLayoutChangeListener> captor =
+                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+        verify(mKeyguardClockSwitch).addOnLayoutChangeListener(captor.capture());
+
+        // Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`.
+        // Below here is the actual test.
+
+        View.OnLayoutChangeListener listener = captor.getValue();
+
+        mController.setSplitShadeEnabled(true);
+        when(mKeyguardClockSwitch.getSplitShadeCentered()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true);
+        when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE);
+        when(mMediaHostContainer.getHeight()).thenReturn(200);
+        when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition);
+
+        when(mKeyguardClockSwitch.getHeight()).thenReturn(200);
+        listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */
+                0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
+                0, /* oldBottom = */ 200);
+        verify(mMediaLayoutTransition, never()).enableTransitionType(LayoutTransition.CHANGING);
+    }
+
+    @Test
+    public void onMediaHostContainerLayout_disablesChangingLayoutTransition() {
+        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
+                mMediaHostContainer);
+
+        mController.onInit();
+
+        ArgumentCaptor<View.OnLayoutChangeListener> captor =
+                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+        verify(mMediaHostContainer).addOnLayoutChangeListener(captor.capture());
+
+        // Above here is the same as `onInit_addsOnLayoutChangeListenerToMediaHostContainer`.
+        // Below here is the actual test.
+
+        View.OnLayoutChangeListener listener = captor.getValue();
+
+        when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition);
+
+        when(mMediaLayoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGING)).thenReturn(
+                true);
+        listener.onLayoutChange(mMediaHostContainer, 1, 2, 3, 4, 1, 2, 3, 4);
+        verify(mMediaLayoutTransition).disableTransitionType(LayoutTransition.CHANGING);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index 86439e5..58d372c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -1,5 +1,6 @@
 package com.android.keyguard
 
+import android.animation.LayoutTransition
 import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
@@ -35,6 +36,20 @@
     }
 
     @Test
+    fun mediaViewHasLayoutTransitionInDisabledState() {
+        val layoutTransition = (mediaView as ViewGroup).layoutTransition
+        assertThat(layoutTransition).isNotNull()
+        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGE_APPEARING))
+            .isFalse()
+        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGE_DISAPPEARING))
+            .isFalse()
+        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.APPEARING)).isFalse()
+        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.DISAPPEARING))
+            .isFalse()
+        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGING)).isFalse()
+    }
+
+    @Test
     fun setChildrenTranslationYExcludingMediaView_mediaViewIsNotTranslated() {
         val translationY = 1234f
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt
similarity index 72%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt
index 93f316e..9e5d3bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt
@@ -29,15 +29,16 @@
 import org.mockito.Mockito.mock
 
 @SmallTest
-class QSFragmentDisableFlagsLoggerTest : SysuiTestCase() {
+class QSDisableFlagsLoggerTest : SysuiTestCase() {
 
-    private val buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
-        .create("buffer", 10)
-    private val disableFlagsLogger = DisableFlagsLogger(
-        listOf(DisableFlagsLogger.DisableFlag(0b001, 'A', 'a')),
-        listOf(DisableFlagsLogger.DisableFlag(0b001, 'B', 'b'))
-    )
-    private val logger = QSFragmentDisableFlagsLogger(buffer, disableFlagsLogger)
+    private val buffer =
+        LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java)).create("buffer", 10)
+    private val disableFlagsLogger =
+        DisableFlagsLogger(
+            listOf(DisableFlagsLogger.DisableFlag(0b001, 'A', 'a')),
+            listOf(DisableFlagsLogger.DisableFlag(0b001, 'B', 'b'))
+        )
+    private val logger = QSDisableFlagsLogger(buffer, disableFlagsLogger)
 
     @Test
     fun logDisableFlagChange_bufferHasStates() {
@@ -48,9 +49,8 @@
         val stringWriter = StringWriter()
         buffer.dump(PrintWriter(stringWriter), tailLength = 0)
         val actualString = stringWriter.toString()
-        val expectedLogString = disableFlagsLogger.getDisableFlagsString(
-            new = state, newAfterLocalModification = state
-        )
+        val expectedLogString =
+            disableFlagsLogger.getDisableFlagsString(new = state, newAfterLocalModification = state)
 
         assertThat(actualString).contains(expectedLogString)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
similarity index 67%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index c4c233c..d57765c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
@@ -1,15 +1,17 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
+ * 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.
+ * 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.systemui.qs;
@@ -34,7 +36,6 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
-import android.app.Fragment;
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -49,12 +50,12 @@
 
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.res.R;
-import com.android.systemui.SysuiBaseFragmentTest;
+import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.media.controls.ui.MediaHost;
 import com.android.systemui.qs.customize.QSCustomizerController;
-import com.android.systemui.qs.dagger.QSFragmentComponent;
+import com.android.systemui.qs.dagger.QSComponent;
 import com.android.systemui.qs.external.TileServiceRequestController;
 import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
@@ -66,8 +67,8 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
+import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.util.animation.UniqueObjectHostView;
 
 import org.junit.Before;
@@ -79,10 +80,9 @@
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
-public class QSFragmentTest extends SysuiBaseFragmentTest {
+public class QSImplTest extends SysuiTestCase {
 
-    @Mock private QSFragmentComponent.Factory mQsComponentFactory;
-    @Mock private QSFragmentComponent mQsFragmentComponent;
+    @Mock private QSComponent mQsComponent;
     @Mock private QSPanelController mQSPanelController;
     @Mock private MediaHost mQSMediaHost;
     @Mock private MediaHost mQQSMediaHost;
@@ -107,69 +107,54 @@
     @Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
     @Mock private FooterActionsViewBinder mFooterActionsViewBinder;
     @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
-    @Mock private FeatureFlags mFeatureFlags;
-    private View mQsFragmentView;
+    @Mock private FeatureFlagsClassic mFeatureFlags;
+    private View mQsView;
 
-    public QSFragmentTest() {
-        super(QSFragment.class);
-    }
+    private QSImpl mUnderTest;
+
 
     @Before
     public void setup() {
-        injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
+        mUnderTest = instantiate();
+
+        mUnderTest.onComponentCreated(mQsComponent, null);
     }
 
-    @Test
-    public void testListening() {
-        QSFragment qs = (QSFragment) mFragment;
-        mFragments.dispatchResume();
-        processAllMessages();
-
-        qs.setListening(true);
-        processAllMessages();
-
-        qs.setListening(false);
-        processAllMessages();
-    }
 
     @Test
     public void testSaveState() {
-        mFragments.dispatchResume();
-        processAllMessages();
+        mUnderTest.setListening(true);
+        mUnderTest.setExpanded(true);
+        mUnderTest.setQsVisible(true);
 
-        QSFragment qs = (QSFragment) mFragment;
-        qs.setListening(true);
-        qs.setExpanded(true);
-        qs.setQsVisible(true);
-        processAllMessages();
-        recreateFragment();
-        processAllMessages();
+        Bundle bundle = new Bundle();
+        mUnderTest.onSaveInstanceState(bundle);
 
-        // Get the reference to the new fragment.
-        qs = (QSFragment) mFragment;
-        assertTrue(qs.isListening());
-        assertTrue(qs.isExpanded());
-        assertTrue(qs.isQsVisible());
+        // Get a new instance
+        QSImpl other = instantiate();
+        other.onComponentCreated(mQsComponent, bundle);
+
+        assertTrue(other.isListening());
+        assertTrue(other.isExpanded());
+        assertTrue(other.isQsVisible());
     }
 
     @Test
     public void transitionToFullShade_smallScreen_alphaAlways1() {
-        QSFragment fragment = resumeAndGetFragment();
         setIsSmallScreen();
         setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
         boolean isTransitioningToFullShade = true;
         float transitionProgress = 0.5f;
         float squishinessFraction = 0.5f;
 
-        fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+        mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
                 squishinessFraction);
 
-        assertThat(mQsFragmentView.getAlpha()).isEqualTo(1f);
+        assertThat(mQsView.getAlpha()).isEqualTo(1f);
     }
 
     @Test
     public void transitionToFullShade_largeScreen_alphaLargeScreenShadeInterpolator() {
-        QSFragment fragment = resumeAndGetFragment();
         setIsLargeScreen();
         setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
         boolean isTransitioningToFullShade = true;
@@ -177,43 +162,40 @@
         float squishinessFraction = 0.5f;
         when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f);
 
-        fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+        mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
                 squishinessFraction);
 
-        assertThat(mQsFragmentView.getAlpha())
-                .isEqualTo(123f);
+        assertThat(mQsView.getAlpha()).isEqualTo(123f);
     }
 
     @Test
     public void
             transitionToFullShade_onKeyguard_noBouncer_setsAlphaUsingLinearInterpolator() {
-        QSFragment fragment = resumeAndGetFragment();
         setStatusBarCurrentAndUpcomingState(KEYGUARD);
         when(mQSPanelController.isBouncerInTransit()).thenReturn(false);
         boolean isTransitioningToFullShade = true;
         float transitionProgress = 0.5f;
         float squishinessFraction = 0.5f;
 
-        fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+        mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
                 squishinessFraction);
 
-        assertThat(mQsFragmentView.getAlpha()).isEqualTo(transitionProgress);
+        assertThat(mQsView.getAlpha()).isEqualTo(transitionProgress);
     }
 
     @Test
     public void
             transitionToFullShade_onKeyguard_bouncerActive_setsAlphaUsingBouncerInterpolator() {
-        QSFragment fragment = resumeAndGetFragment();
         setStatusBarCurrentAndUpcomingState(KEYGUARD);
         when(mQSPanelController.isBouncerInTransit()).thenReturn(true);
         boolean isTransitioningToFullShade = true;
         float transitionProgress = 0.5f;
         float squishinessFraction = 0.5f;
 
-        fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+        mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
                 squishinessFraction);
 
-        assertThat(mQsFragmentView.getAlpha())
+        assertThat(mQsView.getAlpha())
                 .isEqualTo(
                         BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(
                                 transitionProgress));
@@ -221,40 +203,37 @@
 
     @Test
     public void transitionToFullShade_inFullWidth_alwaysSetsAlphaTo1() {
-        QSFragment fragment = resumeAndGetFragment();
-        fragment.setIsNotificationPanelFullWidth(true);
+        mUnderTest.setIsNotificationPanelFullWidth(true);
 
         boolean isTransitioningToFullShade = true;
         float transitionProgress = 0.1f;
         float squishinessFraction = 0.5f;
-        fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+        mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
                 squishinessFraction);
-        assertThat(mQsFragmentView.getAlpha()).isEqualTo(1);
+        assertThat(mQsView.getAlpha()).isEqualTo(1);
 
         transitionProgress = 0.5f;
-        fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+        mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
                 squishinessFraction);
-        assertThat(mQsFragmentView.getAlpha()).isEqualTo(1);
-        assertThat(mQsFragmentView.getAlpha()).isEqualTo(1);
+        assertThat(mQsView.getAlpha()).isEqualTo(1);
+        assertThat(mQsView.getAlpha()).isEqualTo(1);
 
         transitionProgress = 0.7f;
-        fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+        mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
                 squishinessFraction);
-        assertThat(mQsFragmentView.getAlpha()).isEqualTo(1);
+        assertThat(mQsView.getAlpha()).isEqualTo(1);
     }
 
     @Test
     public void transitionToFullShade_setsSquishinessOnController() {
-        QSFragment fragment = resumeAndGetFragment();
         boolean isTransitioningToFullShade = true;
         float transitionProgress = 0.123f;
         float squishinessFraction = 0.456f;
 
-        fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+        mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
                 squishinessFraction);
 
-        verify(mQsFragmentComponent.getQSSquishinessController())
-                .setSquishiness(squishinessFraction);
+        verify(mQsComponent.getQSSquishinessController()).setSquishiness(squishinessFraction);
     }
 
     @Test
@@ -265,10 +244,9 @@
         float proposedTranslation = 456f;
         float squishinessFraction = 0.987f;
 
-        QSFragment fragment = resumeAndGetFragment();
         enableSplitShade();
 
-        fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+        mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
                 squishinessFraction);
 
         verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged(
@@ -283,10 +261,9 @@
         float proposedTranslation = 456f;
         float squishinessFraction = 0.987f;
 
-        QSFragment fragment = resumeAndGetFragment();
         disableSplitShade();
 
-        fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+        mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
                 squishinessFraction);
 
         verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged(
@@ -295,7 +272,6 @@
 
     @Test
     public void setQsExpansion_inSplitShade_whenTransitioningToKeyguard_setsAlphaBasedOnShadeTransitionProgress() {
-        QSFragment fragment = resumeAndGetFragment();
         enableSplitShade();
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
         when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
@@ -303,24 +279,23 @@
         float transitionProgress = 0;
         float squishinessFraction = 0f;
 
-        fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+        mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
                 squishinessFraction);
 
         // trigger alpha refresh with non-zero expansion and fraction values
-        fragment.setQsExpansion(/* expansion= */ 1, /* panelExpansionFraction= */1,
+        mUnderTest.setQsExpansion(/* expansion= */ 1, /* panelExpansionFraction= */1,
                 /* proposedTranslation= */ 0, /* squishinessFraction= */ 1);
 
         // alpha should follow lockscreen to shade progress, not panel expansion fraction
-        assertThat(mQsFragmentView.getAlpha()).isEqualTo(transitionProgress);
+        assertThat(mQsView.getAlpha()).isEqualTo(transitionProgress);
     }
 
     @Test
     public void getQsMinExpansionHeight_notInSplitShade_returnsHeaderHeight() {
-        QSFragment fragment = resumeAndGetFragment();
         disableSplitShade();
         when(mHeader.getHeight()).thenReturn(1234);
 
-        int height = fragment.getQsMinExpansionHeight();
+        int height = mUnderTest.getQsMinExpansionHeight();
 
         assertThat(height).isEqualTo(mHeader.getHeight());
     }
@@ -329,13 +304,12 @@
     public void getQsMinExpansionHeight_inSplitShade_returnsAbsoluteBottomOfQSContainer() {
         int top = 1234;
         int height = 9876;
-        QSFragment fragment = resumeAndGetFragment();
         enableSplitShade();
-        setLocationOnScreen(mQsFragmentView, top);
-        when(mQsFragmentView.getHeight()).thenReturn(height);
+        setLocationOnScreen(mQsView, top);
+        when(mQsView.getHeight()).thenReturn(height);
 
         int expectedHeight = top + height;
-        assertThat(fragment.getQsMinExpansionHeight()).isEqualTo(expectedHeight);
+        assertThat(mUnderTest.getQsMinExpansionHeight()).isEqualTo(expectedHeight);
     }
 
     @Test
@@ -343,47 +317,43 @@
         int top = 1234;
         int height = 9876;
         float translationY = -600f;
-        QSFragment fragment = resumeAndGetFragment();
         enableSplitShade();
-        setLocationOnScreen(mQsFragmentView, (int) (top + translationY));
-        when(mQsFragmentView.getHeight()).thenReturn(height);
-        when(mQsFragmentView.getTranslationY()).thenReturn(translationY);
+        setLocationOnScreen(mQsView, (int) (top + translationY));
+        when(mQsView.getHeight()).thenReturn(height);
+        when(mQsView.getTranslationY()).thenReturn(translationY);
 
         int expectedHeight = top + height;
-        assertThat(fragment.getQsMinExpansionHeight()).isEqualTo(expectedHeight);
+        assertThat(mUnderTest.getQsMinExpansionHeight()).isEqualTo(expectedHeight);
     }
 
     @Test
     public void hideImmediately_notInSplitShade_movesViewUpByHeaderHeight() {
-        QSFragment fragment = resumeAndGetFragment();
         disableSplitShade();
         when(mHeader.getHeight()).thenReturn(555);
 
-        fragment.hideImmediately();
+        mUnderTest.hideImmediately();
 
-        assertThat(mQsFragmentView.getY()).isEqualTo(-mHeader.getHeight());
+        assertThat(mQsView.getY()).isEqualTo(-mHeader.getHeight());
     }
 
     @Test
     public void hideImmediately_inSplitShade_movesViewUpByQSAbsoluteBottom() {
-        QSFragment fragment = resumeAndGetFragment();
         enableSplitShade();
         int top = 1234;
         int height = 9876;
-        setLocationOnScreen(mQsFragmentView, top);
-        when(mQsFragmentView.getHeight()).thenReturn(height);
+        setLocationOnScreen(mQsView, top);
+        when(mQsView.getHeight()).thenReturn(height);
 
-        fragment.hideImmediately();
+        mUnderTest.hideImmediately();
 
         int qsAbsoluteBottom = top + height;
-        assertThat(mQsFragmentView.getY()).isEqualTo(-qsAbsoluteBottom);
+        assertThat(mQsView.getY()).isEqualTo(-qsAbsoluteBottom);
     }
 
     @Test
     public void setCollapseExpandAction_passedToControllers() {
         Runnable action = () -> {};
-        QSFragment fragment = resumeAndGetFragment();
-        fragment.setCollapseExpandAction(action);
+        mUnderTest.setCollapseExpandAction(action);
 
         verify(mQSPanelController).setCollapseExpandAction(action);
         verify(mQuickQSPanelController).setCollapseExpandAction(action);
@@ -391,24 +361,21 @@
 
     @Test
     public void setOverScrollAmount_setsTranslationOnView() {
-        QSFragment fragment = resumeAndGetFragment();
+        mUnderTest.setOverScrollAmount(123);
 
-        fragment.setOverScrollAmount(123);
-
-        assertThat(mQsFragmentView.getTranslationY()).isEqualTo(123);
+        assertThat(mQsView.getTranslationY()).isEqualTo(123);
     }
 
     @Test
     public void setOverScrollAmount_beforeViewCreated_translationIsNotSet() {
-        QSFragment fragment = getFragment();
+        QSImpl other = instantiate();
+        other.setOverScrollAmount(123);
 
-        fragment.setOverScrollAmount(123);
-
-        assertThat(mQsFragmentView.getTranslationY()).isEqualTo(0);
+        assertThat(mQsView.getTranslationY()).isEqualTo(0);
     }
 
     private Lifecycle.State getListeningAndVisibilityLifecycleState() {
-        return getFragment()
+        return mUnderTest
                 .getListeningAndVisibilityLifecycleOwner()
                 .getLifecycle()
                 .getCurrentState();
@@ -416,11 +383,10 @@
 
     @Test
     public void setListeningFalse_notVisible() {
-        QSFragment fragment = resumeAndGetFragment();
-        fragment.setQsVisible(false);
+        mUnderTest.setQsVisible(false);
         clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
 
-        fragment.setListening(false);
+        mUnderTest.setListening(false);
         verify(mQSContainerImplController).setListening(false);
         assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED);
         verify(mQSPanelController).setListening(eq(false), anyBoolean());
@@ -428,11 +394,10 @@
 
     @Test
     public void setListeningTrue_notVisible() {
-        QSFragment fragment = resumeAndGetFragment();
-        fragment.setQsVisible(false);
+        mUnderTest.setQsVisible(false);
         clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
 
-        fragment.setListening(true);
+        mUnderTest.setListening(true);
         verify(mQSContainerImplController).setListening(false);
         assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.STARTED);
         verify(mQSPanelController).setListening(eq(false), anyBoolean());
@@ -440,11 +405,10 @@
 
     @Test
     public void setListeningFalse_visible() {
-        QSFragment fragment = resumeAndGetFragment();
-        fragment.setQsVisible(true);
+        mUnderTest.setQsVisible(true);
         clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
 
-        fragment.setListening(false);
+        mUnderTest.setListening(false);
         verify(mQSContainerImplController).setListening(false);
         assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED);
         verify(mQSPanelController).setListening(eq(false), anyBoolean());
@@ -452,11 +416,10 @@
 
     @Test
     public void setListeningTrue_visible() {
-        QSFragment fragment = resumeAndGetFragment();
-        fragment.setQsVisible(true);
+        mUnderTest.setQsVisible(true);
         clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
 
-        fragment.setListening(true);
+        mUnderTest.setListening(true);
         verify(mQSContainerImplController).setListening(true);
         assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.RESUMED);
         verify(mQSPanelController).setListening(eq(true), anyBoolean());
@@ -464,31 +427,28 @@
 
     @Test
     public void passCorrectExpansionState_inSplitShade() {
-        QSFragment fragment = resumeAndGetFragment();
         enableSplitShade();
         clearInvocations(mQSPanelController);
 
-        fragment.setExpanded(true);
+        mUnderTest.setExpanded(true);
         verify(mQSPanelController).setExpanded(true);
 
-        fragment.setExpanded(false);
+        mUnderTest.setExpanded(false);
         verify(mQSPanelController).setExpanded(false);
     }
 
     @Test
     public void startsListeningAfterStateChangeToExpanded_inSplitShade() {
-        QSFragment fragment = resumeAndGetFragment();
         enableSplitShade();
-        fragment.setQsVisible(true);
+        mUnderTest.setQsVisible(true);
         clearInvocations(mQSPanelController);
 
-        fragment.setExpanded(true);
+        mUnderTest.setExpanded(true);
         verify(mQSPanelController).setListening(true, true);
     }
 
     @Test
     public void testUpdateQSBounds_setMediaClipCorrectly() {
-        QSFragment fragment = resumeAndGetFragment();
         disableSplitShade();
 
         Rect mediaHostClip = new Rect();
@@ -497,7 +457,7 @@
         when(mQSPanelScrollView.getMeasuredHeight()).thenReturn(200);
         when(mQSMediaHost.getCurrentClipping()).thenReturn(mediaHostClip);
 
-        fragment.updateQsBounds();
+        mUnderTest.updateQsBounds();
 
         assertEquals(25, mediaHostClip.top);
         assertEquals(175, mediaHostClip.bottom);
@@ -505,17 +465,15 @@
 
     @Test
     public void testQsUpdatesQsAnimatorWithUpcomingState() {
-        QSFragment fragment = resumeAndGetFragment();
         setStatusBarCurrentAndUpcomingState(SHADE);
-        fragment.onUpcomingStateChanged(KEYGUARD);
+        mUnderTest.onUpcomingStateChanged(KEYGUARD);
 
         verify(mQSAnimator).setOnKeyguard(true);
     }
 
-    @Override
-    protected Fragment instantiate(Context context, String className, Bundle arguments) {
+    private QSImpl instantiate() {
         MockitoAnnotations.initMocks(this);
-        CommandQueue commandQueue = new CommandQueue(context, new FakeDisplayTracker(context));
+        CommandQueue commandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext));
 
         setupQsComponent();
         setUpViews();
@@ -523,9 +481,9 @@
         setUpMedia();
         setUpOther();
 
-        return new QSFragment(
+        return new QSImpl(
                 new RemoteInputQuickSettingsDisabler(
-                        context,
+                        mContext,
                         commandQueue,
                         new ResourcesSplitShadeStateController(),
                         mock(ConfigurationController.class)),
@@ -534,8 +492,7 @@
                 mQSMediaHost,
                 mQQSMediaHost,
                 mBypassController,
-                mQsComponentFactory,
-                mock(QSFragmentDisableFlagsLogger.class),
+                mock(QSDisableFlagsLogger.class),
                 mock(DumpManager.class),
                 mock(QSLogger.class),
                 mock(FooterActionsController.class),
@@ -561,12 +518,13 @@
     }
 
     private void setUpViews() {
-        mQsFragmentView = spy(new View(mContext));
-        when(mQsFragmentView.findViewById(R.id.expanded_qs_scroll_view))
+        mQsView = spy(new View(mContext));
+        when(mQsComponent.getRootView()).thenReturn(mQsView);
+        when(mQsView.findViewById(R.id.expanded_qs_scroll_view))
                 .thenReturn(mQSPanelScrollView);
-        when(mQsFragmentView.findViewById(R.id.header)).thenReturn(mHeader);
-        when(mQsFragmentView.findViewById(android.R.id.edit)).thenReturn(new View(mContext));
-        when(mQsFragmentView.findViewById(R.id.qs_footer_actions)).thenAnswer(
+        when(mQsView.findViewById(R.id.header)).thenReturn(mHeader);
+        when(mQsView.findViewById(android.R.id.edit)).thenReturn(new View(mContext));
+        when(mQsView.findViewById(R.id.qs_footer_actions)).thenAnswer(
                 invocation -> new FooterActionsViewBinder().create(mContext));
     }
 
@@ -597,37 +555,26 @@
             return realInflater.inflate(layoutRes, root, attachToRoot);
         }
 
-        return mQsFragmentView;
+        return mQsView;
     }
 
     private void setupQsComponent() {
-        when(mQsComponentFactory.create(any(QSFragment.class))).thenReturn(mQsFragmentComponent);
-        when(mQsFragmentComponent.getQSPanelController()).thenReturn(mQSPanelController);
-        when(mQsFragmentComponent.getQuickQSPanelController()).thenReturn(mQuickQSPanelController);
-        when(mQsFragmentComponent.getQSCustomizerController()).thenReturn(mQsCustomizerController);
-        when(mQsFragmentComponent.getQSContainerImplController())
+        when(mQsComponent.getQSPanelController()).thenReturn(mQSPanelController);
+        when(mQsComponent.getQuickQSPanelController()).thenReturn(mQuickQSPanelController);
+        when(mQsComponent.getQSCustomizerController()).thenReturn(mQsCustomizerController);
+        when(mQsComponent.getQSContainerImplController())
                 .thenReturn(mQSContainerImplController);
-        when(mQsFragmentComponent.getQSFooter()).thenReturn(mFooter);
-        when(mQsFragmentComponent.getQSFooterActionController())
+        when(mQsComponent.getQSFooter()).thenReturn(mFooter);
+        when(mQsComponent.getQSFooterActionController())
                 .thenReturn(mQSFooterActionController);
-        when(mQsFragmentComponent.getQSAnimator()).thenReturn(mQSAnimator);
-        when(mQsFragmentComponent.getQSSquishinessController()).thenReturn(mSquishinessController);
-    }
-
-    private QSFragment getFragment() {
-        return ((QSFragment) mFragment);
-    }
-
-    private QSFragment resumeAndGetFragment() {
-        mFragments.dispatchResume();
-        processAllMessages();
-        return getFragment();
+        when(mQsComponent.getQSAnimator()).thenReturn(mQSAnimator);
+        when(mQsComponent.getQSSquishinessController()).thenReturn(mSquishinessController);
     }
 
     private void setStatusBarCurrentAndUpcomingState(int statusBarState) {
         when(mStatusBarStateController.getState()).thenReturn(statusBarState);
         when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(statusBarState);
-        getFragment().onStateChanged(statusBarState);
+        mUnderTest.onStateChanged(statusBarState);
     }
 
     private void enableSplitShade() {
@@ -639,7 +586,7 @@
     }
 
     private void setSplitShadeEnabled(boolean enabled) {
-        getFragment().setInSplitShade(enabled);
+        mUnderTest.setInSplitShade(enabled);
     }
 
     private void setLocationOnScreen(View view, int top) {
@@ -652,10 +599,10 @@
     }
 
     private void setIsLargeScreen() {
-        getFragment().setIsNotificationPanelFullWidth(false);
+        mUnderTest.setIsNotificationPanelFullWidth(false);
     }
 
     private void setIsSmallScreen() {
-        getFragment().setIsNotificationPanelFullWidth(true);
+        mUnderTest.setIsNotificationPanelFullWidth(true);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index b595e8d..79411f42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -66,6 +66,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.qs.tiles.di.NewQSTileFactory;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
@@ -77,6 +78,8 @@
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 
+import dagger.Lazy;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -102,8 +105,6 @@
     private static final String SETTING = QSHost.TILES_SETTING;
 
     @Mock
-    private QSFactory mDefaultFactory;
-    @Mock
     private PluginManager mPluginManager;
     @Mock
     private TunerService mTunerService;
@@ -117,7 +118,6 @@
     private CustomTile mCustomTile;
     @Mock
     private UserTracker mUserTracker;
-    private SecureSettings mSecureSettings;
     @Mock
     private CustomTileStatePersister mCustomTileStatePersister;
     @Mock
@@ -127,6 +127,10 @@
     @Mock
     private UserFileManager mUserFileManager;
 
+    private SecureSettings mSecureSettings;
+
+    private QSFactory mDefaultFactory;
+
     private SparseArray<SharedPreferences> mSharedPreferencesByUser;
 
     private FakeFeatureFlags mFeatureFlags;
@@ -144,6 +148,8 @@
 
         mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, false);
         mFeatureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, false);
+        // TODO(b/299909337): Add test checking the new factory is used when the flag is on
+        mFeatureFlags.set(Flags.QS_PIPELINE_NEW_TILES, false);
         mQSPipelineFlagsRepository = new QSPipelineFlagsRepository(mFeatureFlags);
 
         mMainExecutor = new FakeExecutor(new FakeSystemClock());
@@ -164,7 +170,8 @@
 
         mSecureSettings = new FakeSettings();
         saveSetting("");
-        mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
+        setUpTileFactory();
+        mQSTileHost = new TestQSTileHost(mContext, () -> null, mDefaultFactory, mMainExecutor,
                 mPluginManager, mTunerService, () -> mAutoTiles, mShadeController,
                 mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
                 mTileLifecycleManagerFactory, mUserFileManager, mQSPipelineFlagsRepository);
@@ -178,7 +185,6 @@
                 mMainExecutor.runAllReady();
             }
         }, mUserTracker.getUserId());
-        setUpTileFactory();
     }
 
     private void saveSetting(String value) {
@@ -191,32 +197,29 @@
     }
 
     private void setUpTileFactory() {
-        // Only create this kind of tiles
-        when(mDefaultFactory.createTile(anyString())).thenAnswer(
-                invocation -> {
-                    String spec = invocation.getArgument(0);
-                    if ("spec1".equals(spec)) {
-                        return new TestTile1(mQSTileHost);
-                    } else if ("spec2".equals(spec)) {
-                        return new TestTile2(mQSTileHost);
-                    } else if ("spec3".equals(spec)) {
-                        return new TestTile3(mQSTileHost);
-                    } else if ("na".equals(spec)) {
-                        return new NotAvailableTile(mQSTileHost);
-                    } else if (CUSTOM_TILE_SPEC.equals(spec)) {
-                        QSTile tile = mCustomTile;
-                        QSTile.State s = mock(QSTile.State.class);
-                        s.spec = spec;
-                        when(mCustomTile.getState()).thenReturn(s);
-                        return tile;
-                    } else if ("internet".equals(spec)
-                            || "wifi".equals(spec)
-                            || "cell".equals(spec)) {
-                        return new TestTile1(mQSTileHost);
-                    } else {
-                        return null;
-                    }
-                });
+        mDefaultFactory = new FakeQSFactory(spec -> {
+            if ("spec1".equals(spec)) {
+                return new TestTile1(mQSTileHost);
+            } else if ("spec2".equals(spec)) {
+                return new TestTile2(mQSTileHost);
+            } else if ("spec3".equals(spec)) {
+                return new TestTile3(mQSTileHost);
+            } else if ("na".equals(spec)) {
+                return new NotAvailableTile(mQSTileHost);
+            } else if (CUSTOM_TILE_SPEC.equals(spec)) {
+                QSTile tile = mCustomTile;
+                QSTile.State s = mock(QSTile.State.class);
+                s.spec = spec;
+                when(mCustomTile.getState()).thenReturn(s);
+                return tile;
+            } else if ("internet".equals(spec)
+                    || "wifi".equals(spec)
+                    || "cell".equals(spec)) {
+                return new TestTile1(mQSTileHost);
+            } else {
+                return null;
+            }
+        });
         when(mCustomTile.isAvailable()).thenReturn(true);
     }
 
@@ -703,7 +706,7 @@
     }
 
     private class TestQSTileHost extends QSTileHost {
-        TestQSTileHost(Context context,
+        TestQSTileHost(Context context, Lazy<NewQSTileFactory> newQSTileFactoryProvider,
                 QSFactory defaultFactory, Executor mainExecutor,
                 PluginManager pluginManager, TunerService tunerService,
                 Provider<AutoTileManager> autoTiles,
@@ -712,7 +715,7 @@
                 CustomTileStatePersister customTileStatePersister,
                 TileLifecycleManager.Factory tileLifecycleManagerFactory,
                 UserFileManager userFileManager, QSPipelineFlagsRepository featureFlags) {
-            super(context, defaultFactory, mainExecutor, pluginManager,
+            super(context, newQSTileFactoryProvider, defaultFactory, mainExecutor, pluginManager,
                     tunerService, autoTiles,  shadeController, qsLogger,
                     userTracker, secureSettings, customTileStatePersister,
                     tileLifecycleManagerFactory, userFileManager, featureFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index bde3038..d3cd26b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -124,6 +124,7 @@
                     if (FACTORY_TILES.contains(spec)) {
                         FakeQSTile tile = new FakeQSTile(mBgExecutor, mMainExecutor);
                         tile.setState(mState);
+                        tile.setTileSpec(spec);
                         return tile;
                     } else {
                         return null;
@@ -284,7 +285,10 @@
         Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, null);
 
         QSTile t = mock(QSTile.class);
-        when(mQSHost.createTile("hotspot")).thenReturn(t);
+        when(mQSHost.createTile("hotspot")).thenAnswer(invocation -> {
+            t.setTileSpec("hotspot");
+            return t;
+        });
 
         mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
                 "hotspot");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
index 9386d71..9a55f72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
@@ -23,15 +23,19 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -39,6 +43,20 @@
 @RunWith(AndroidJUnit4::class)
 class AutoAddSettingsRepositoryTest : SysuiTestCase() {
     private val secureSettings = FakeSettings()
+    private val userAutoAddRepositoryFactory =
+        object : UserAutoAddRepository.Factory {
+            override fun create(userId: Int): UserAutoAddRepository {
+                return UserAutoAddRepository(
+                    userId,
+                    secureSettings,
+                    logger,
+                    testScope.backgroundScope,
+                    testDispatcher,
+                )
+            }
+        }
+
+    @Mock private lateinit var logger: QSPipelineLogger
 
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
@@ -47,110 +65,37 @@
 
     @Before
     fun setUp() {
-        underTest =
-            AutoAddSettingRepository(
-                secureSettings,
-                testDispatcher,
-            )
+        MockitoAnnotations.initMocks(this)
+
+        underTest = AutoAddSettingRepository(userAutoAddRepositoryFactory)
     }
 
     @Test
-    fun nonExistentSetting_emptySet() =
-        testScope.runTest {
-            val specs by collectLastValue(underTest.autoAddedTiles(0))
-
-            assertThat(specs).isEmpty()
-        }
-
-    @Test
-    fun settingsChange_correctValues() =
-        testScope.runTest {
-            val userId = 0
-            val specs by collectLastValue(underTest.autoAddedTiles(userId))
-
-            val value = "a,custom(b/c)"
-            storeForUser(value, userId)
-
-            assertThat(specs).isEqualTo(value.toSet())
-
-            val newValue = "a"
-            storeForUser(newValue, userId)
-
-            assertThat(specs).isEqualTo(newValue.toSet())
-        }
-
-    @Test
     fun tilesForCorrectUsers() =
         testScope.runTest {
-            val tilesFromUser0 by collectLastValue(underTest.autoAddedTiles(0))
-            val tilesFromUser1 by collectLastValue(underTest.autoAddedTiles(1))
-
             val user0Tiles = "a"
             val user1Tiles = "custom(b/c)"
             storeForUser(user0Tiles, 0)
             storeForUser(user1Tiles, 1)
+            val tilesFromUser0 by collectLastValue(underTest.autoAddedTiles(0))
+            val tilesFromUser1 by collectLastValue(underTest.autoAddedTiles(1))
+            runCurrent()
 
-            assertThat(tilesFromUser0).isEqualTo(user0Tiles.toSet())
-            assertThat(tilesFromUser1).isEqualTo(user1Tiles.toSet())
-        }
-
-    @Test
-    fun noInvalidTileSpecs() =
-        testScope.runTest {
-            val userId = 0
-            val tiles by collectLastValue(underTest.autoAddedTiles(userId))
-
-            val specs = "d,custom(bad)"
-            storeForUser(specs, userId)
-
-            assertThat(tiles).isEqualTo("d".toSet())
-        }
-
-    @Test
-    fun markAdded() =
-        testScope.runTest {
-            val userId = 0
-            val specs = mutableSetOf(TileSpec.create("a"))
-            underTest.markTileAdded(userId, TileSpec.create("a"))
-
-            assertThat(loadForUser(userId).toSet()).containsExactlyElementsIn(specs)
-
-            specs.add(TileSpec.create("b"))
-            underTest.markTileAdded(userId, TileSpec.create("b"))
-
-            assertThat(loadForUser(userId).toSet()).containsExactlyElementsIn(specs)
+            assertThat(tilesFromUser0).isEqualTo(user0Tiles.toTilesSet())
+            assertThat(tilesFromUser1).isEqualTo(user1Tiles.toTilesSet())
         }
 
     @Test
     fun markAdded_multipleUsers() =
         testScope.runTest {
+            val tilesFromUser0 by collectLastValue(underTest.autoAddedTiles(0))
+            val tilesFromUser1 by collectLastValue(underTest.autoAddedTiles(1))
+            runCurrent()
+
             underTest.markTileAdded(userId = 1, TileSpec.create("a"))
 
-            assertThat(loadForUser(0).toSet()).isEmpty()
-            assertThat(loadForUser(1).toSet())
-                .containsExactlyElementsIn(setOf(TileSpec.create("a")))
-        }
-
-    @Test
-    fun markAdded_Invalid_noop() =
-        testScope.runTest {
-            val userId = 0
-            underTest.markTileAdded(userId, TileSpec.Invalid)
-
-            assertThat(loadForUser(userId).toSet()).isEmpty()
-        }
-
-    @Test
-    fun unmarkAdded() =
-        testScope.runTest {
-            val userId = 0
-            val specs = "a,custom(b/c)"
-            storeForUser(specs, userId)
-
-            underTest.unmarkTileAdded(userId, TileSpec.create("a"))
-
-            assertThat(loadForUser(userId).toSet())
-                .containsExactlyElementsIn(setOf(TileSpec.create("custom(b/c)")))
+            assertThat(tilesFromUser0).isEmpty()
+            assertThat(tilesFromUser1).containsExactlyElementsIn(setOf(TileSpec.create("a")))
         }
 
     @Test
@@ -159,33 +104,23 @@
             val specs = "a,b"
             storeForUser(specs, 0)
             storeForUser(specs, 1)
+            val tilesFromUser0 by collectLastValue(underTest.autoAddedTiles(0))
+            val tilesFromUser1 by collectLastValue(underTest.autoAddedTiles(1))
+            runCurrent()
 
             underTest.unmarkTileAdded(1, TileSpec.create("a"))
 
-            assertThat(loadForUser(0).toSet()).isEqualTo(specs.toSet())
-            assertThat(loadForUser(1).toSet()).isEqualTo(setOf(TileSpec.create("b")))
+            assertThat(tilesFromUser0).isEqualTo(specs.toTilesSet())
+            assertThat(tilesFromUser1).isEqualTo(setOf(TileSpec.create("b")))
         }
 
     private fun storeForUser(specs: String, userId: Int) {
         secureSettings.putStringForUser(SETTING, specs, userId)
     }
 
-    private fun loadForUser(userId: Int): String {
-        return secureSettings.getStringForUser(SETTING, userId) ?: ""
-    }
-
     companion object {
         private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES
-        private const val DELIMITER = ","
 
-        fun Set<TileSpec>.toSeparatedString() = joinToString(DELIMITER, transform = TileSpec::spec)
-
-        fun String.toSet(): Set<TileSpec> {
-            return if (isNullOrBlank()) {
-                emptySet()
-            } else {
-                split(DELIMITER).map(TileSpec::create).toSet()
-            }
-        }
+        private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt
new file mode 100644
index 0000000..dc09a33
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt
@@ -0,0 +1,220 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.content.Intent
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.FakeBroadcastDispatcher
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@RoboPilotTest
+class QSSettingsRestoredBroadcastRepositoryTest : SysuiTestCase() {
+    private val dispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+
+    @Mock private lateinit var pipelineLogger: QSPipelineLogger
+
+    private lateinit var underTest: QSSettingsRestoredBroadcastRepository
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            QSSettingsRestoredBroadcastRepository(
+                fakeBroadcastDispatcher,
+                pipelineLogger,
+                testScope.backgroundScope,
+                dispatcher,
+            )
+    }
+
+    @Test
+    fun restoreDataAfterBothIntents_tilesRestoredFirst() =
+        testScope.runTest {
+            runCurrent()
+            val restoreData by collectLastValue(underTest.restoreData)
+            val user = 0
+
+            val tilesIntent =
+                createRestoreIntent(
+                    RestoreType.TILES,
+                    CURRENT_TILES,
+                    RESTORED_TILES,
+                )
+
+            val autoAddIntent =
+                createRestoreIntent(
+                    RestoreType.AUTOADD,
+                    CURRENT_AUTO_ADDED_TILES,
+                    RESTORED_AUTO_ADDED_TILES,
+                )
+
+            sendIntentForUser(tilesIntent, user)
+
+            // No restore data yet as we are missing one of the broadcasts
+            assertThat(restoreData).isNull()
+
+            // After the second event, we see the corresponding restore
+            sendIntentForUser(autoAddIntent, user)
+
+            with(restoreData!!) {
+                assertThat(restoredTiles).isEqualTo(RESTORED_TILES.toTilesList())
+                assertThat(restoredAutoAddedTiles).isEqualTo(RESTORED_AUTO_ADDED_TILES.toTilesSet())
+                assertThat(userId).isEqualTo(user)
+            }
+        }
+
+    @Test
+    fun restoreDataAfterBothIntents_autoAddRestoredFirst() =
+        testScope.runTest {
+            runCurrent()
+            val restoreData by collectLastValue(underTest.restoreData)
+            val user = 0
+
+            val tilesIntent =
+                createRestoreIntent(
+                    RestoreType.TILES,
+                    CURRENT_TILES,
+                    RESTORED_TILES,
+                )
+
+            val autoAddIntent =
+                createRestoreIntent(
+                    RestoreType.AUTOADD,
+                    CURRENT_AUTO_ADDED_TILES,
+                    RESTORED_AUTO_ADDED_TILES,
+                )
+
+            sendIntentForUser(autoAddIntent, user)
+
+            // No restore data yet as we are missing one of the broadcasts
+            assertThat(restoreData).isNull()
+
+            // After the second event, we see the corresponding restore
+            sendIntentForUser(tilesIntent, user)
+
+            with(restoreData!!) {
+                assertThat(restoredTiles).isEqualTo(RESTORED_TILES.toTilesList())
+                assertThat(restoredAutoAddedTiles).isEqualTo(RESTORED_AUTO_ADDED_TILES.toTilesSet())
+                assertThat(userId).isEqualTo(user)
+            }
+        }
+
+    @Test
+    fun interleavedBroadcastsFromDifferentUsers_onlysendDataForCorrectUser() =
+        testScope.runTest {
+            runCurrent()
+            val restoreData by collectLastValue(underTest.restoreData)
+
+            val user0 = 0
+            val user10 = 10
+
+            val currentTiles10 = "z,y,x"
+            val restoredTiles10 = "x"
+            val currentAutoAdded10 = "f"
+            val restoredAutoAdded10 = "f,g"
+
+            val tilesIntent0 =
+                createRestoreIntent(
+                    RestoreType.TILES,
+                    CURRENT_TILES,
+                    RESTORED_TILES,
+                )
+            val autoAddIntent0 =
+                createRestoreIntent(
+                    RestoreType.AUTOADD,
+                    CURRENT_AUTO_ADDED_TILES,
+                    RESTORED_AUTO_ADDED_TILES,
+                )
+            val tilesIntent10 =
+                createRestoreIntent(
+                    RestoreType.TILES,
+                    currentTiles10,
+                    restoredTiles10,
+                )
+            val autoAddIntent10 =
+                createRestoreIntent(
+                    RestoreType.AUTOADD,
+                    currentAutoAdded10,
+                    restoredAutoAdded10,
+                )
+
+            sendIntentForUser(tilesIntent0, user0)
+            sendIntentForUser(autoAddIntent10, user10)
+            assertThat(restoreData).isNull()
+
+            sendIntentForUser(tilesIntent10, user10)
+
+            with(restoreData!!) {
+                assertThat(restoredTiles).isEqualTo(restoredTiles10.toTilesList())
+                assertThat(restoredAutoAddedTiles).isEqualTo(restoredAutoAdded10.toTilesSet())
+                assertThat(userId).isEqualTo(user10)
+            }
+
+            sendIntentForUser(autoAddIntent0, user0)
+
+            with(restoreData!!) {
+                assertThat(restoredTiles).isEqualTo(RESTORED_TILES.toTilesList())
+                assertThat(restoredAutoAddedTiles).isEqualTo(RESTORED_AUTO_ADDED_TILES.toTilesSet())
+                assertThat(userId).isEqualTo(user0)
+            }
+        }
+
+    private fun sendIntentForUser(intent: Intent, userId: Int) {
+        fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+            context,
+            intent,
+            FakeBroadcastDispatcher.fakePendingResultForUser(userId)
+        )
+    }
+
+    companion object {
+        private const val CURRENT_TILES = "a,b,c,d"
+        private const val RESTORED_TILES = "b,a,c"
+        private const val CURRENT_AUTO_ADDED_TILES = "d"
+        private const val RESTORED_AUTO_ADDED_TILES = "e"
+
+        private fun createRestoreIntent(
+            type: RestoreType,
+            previousValue: String,
+            restoredValue: String,
+        ): Intent {
+            val setting =
+                when (type) {
+                    RestoreType.TILES -> Settings.Secure.QS_TILES
+                    RestoreType.AUTOADD -> Settings.Secure.QS_AUTO_ADDED_TILES
+                }
+            return Intent(Intent.ACTION_SETTING_RESTORED)
+                .putExtra(Intent.EXTRA_SETTING_NAME, setting)
+                .putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue)
+                .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, restoredValue)
+        }
+
+        private fun String.toTilesList() = TilesSettingConverter.toTilesList(this)
+
+        private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
+
+        private enum class RestoreType {
+            TILES,
+            AUTOADD,
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 1c28e4c..08adebb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -23,14 +23,12 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import com.android.systemui.retail.data.repository.FakeRetailModeRepository
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -49,9 +47,28 @@
 
     private lateinit var secureSettings: FakeSettings
     private lateinit var retailModeRepository: FakeRetailModeRepository
+    private val defaultTilesRepository =
+        object : DefaultTilesRepository {
+            override val defaultTiles: List<TileSpec>
+                get() = DEFAULT_TILES.toTileSpecs()
+        }
 
     @Mock private lateinit var logger: QSPipelineLogger
 
+    private val userTileSpecRepositoryFactory =
+        object : UserTileSpecRepository.Factory {
+            override fun create(userId: Int): UserTileSpecRepository {
+                return UserTileSpecRepository(
+                    userId,
+                    defaultTilesRepository,
+                    secureSettings,
+                    logger,
+                    testScope.backgroundScope,
+                    testDispatcher,
+                )
+            }
+        }
+
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
@@ -66,293 +83,85 @@
         retailModeRepository.setRetailMode(false)
 
         with(context.orCreateTestableResources) {
-            addOverride(R.string.quick_settings_tiles_default, DEFAULT_TILES)
             addOverride(R.string.quick_settings_tiles_retail_mode, RETAIL_TILES)
         }
 
         underTest =
             TileSpecSettingsRepository(
-                secureSettings,
                 context.resources,
                 logger,
                 retailModeRepository,
-                testDispatcher,
+                userTileSpecRepositoryFactory
             )
     }
 
     @Test
-    fun emptySetting_usesDefaultValue() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-            assertThat(tiles).isEqualTo(getDefaultTileSpecs())
-        }
-
-    @Test
-    fun changeInSettings_changesValue() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-
-            storeTilesForUser("a", 0)
-            assertThat(tiles).isEqualTo(listOf(TileSpec.create("a")))
-
-            storeTilesForUser("a,custom(b/c)", 0)
-            assertThat(tiles)
-                .isEqualTo(listOf(TileSpec.create("a"), TileSpec.create("custom(b/c)")))
-        }
-
-    @Test
     fun tilesForCorrectUsers() =
         testScope.runTest {
-            val tilesFromUser0 by collectLastValue(underTest.tilesSpecs(0))
-            val tilesFromUser1 by collectLastValue(underTest.tilesSpecs(1))
-
             val user0Tiles = "a"
             val user1Tiles = "custom(b/c)"
             storeTilesForUser(user0Tiles, 0)
             storeTilesForUser(user1Tiles, 1)
 
+            val tilesFromUser0 by collectLastValue(underTest.tilesSpecs(0))
+            val tilesFromUser1 by collectLastValue(underTest.tilesSpecs(1))
+
             assertThat(tilesFromUser0).isEqualTo(user0Tiles.toTileSpecs())
             assertThat(tilesFromUser1).isEqualTo(user1Tiles.toTileSpecs())
         }
 
     @Test
-    fun invalidTilesAreNotPresent() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-
-            val specs = "d,custom(bad)"
-            storeTilesForUser(specs, 0)
-
-            assertThat(tiles).isEqualTo(specs.toTileSpecs().filter { it != TileSpec.Invalid })
-        }
-
-    @Test
-    fun noValidTiles_defaultSet() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-
-            storeTilesForUser("custom(bad),custom()", 0)
-
-            assertThat(tiles).isEqualTo(getDefaultTileSpecs())
-        }
-
-    @Test
-    fun addTileAtEnd() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-
-            storeTilesForUser("a", 0)
-
-            underTest.addTile(userId = 0, TileSpec.create("b"))
-
-            val expected = "a,b"
-            assertThat(loadTilesForUser(0)).isEqualTo(expected)
-            assertThat(tiles).isEqualTo(expected.toTileSpecs())
-        }
-
-    @Test
-    fun addTileAtPosition() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-
-            storeTilesForUser("a,custom(b/c)", 0)
-
-            underTest.addTile(userId = 0, TileSpec.create("d"), position = 1)
-
-            val expected = "a,d,custom(b/c)"
-            assertThat(loadTilesForUser(0)).isEqualTo(expected)
-            assertThat(tiles).isEqualTo(expected.toTileSpecs())
-        }
-
-    @Test
-    fun addInvalidTile_noop() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-
-            val specs = "a,custom(b/c)"
-            storeTilesForUser(specs, 0)
-
-            underTest.addTile(userId = 0, TileSpec.Invalid)
-
-            assertThat(loadTilesForUser(0)).isEqualTo(specs)
-            assertThat(tiles).isEqualTo(specs.toTileSpecs())
-        }
-
-    @Test
-    fun addTileAtPosition_tooLarge_addedAtEnd() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-
-            val specs = "a,custom(b/c)"
-            storeTilesForUser(specs, 0)
-
-            underTest.addTile(userId = 0, TileSpec.create("d"), position = 100)
-
-            val expected = "a,custom(b/c),d"
-            assertThat(loadTilesForUser(0)).isEqualTo(expected)
-            assertThat(tiles).isEqualTo(expected.toTileSpecs())
-        }
-
-    @Test
     fun addTileForOtherUser_addedInThatUser() =
         testScope.runTest {
-            val tilesUser0 by collectLastValue(underTest.tilesSpecs(0))
-            val tilesUser1 by collectLastValue(underTest.tilesSpecs(1))
-
             storeTilesForUser("a", 0)
             storeTilesForUser("b", 1)
+            val tilesUser0 by collectLastValue(underTest.tilesSpecs(0))
+            val tilesUser1 by collectLastValue(underTest.tilesSpecs(1))
+            runCurrent()
 
             underTest.addTile(userId = 1, TileSpec.create("c"))
 
-            assertThat(loadTilesForUser(0)).isEqualTo("a")
             assertThat(tilesUser0).isEqualTo("a".toTileSpecs())
-            assertThat(loadTilesForUser(1)).isEqualTo("b,c")
+            assertThat(loadTilesForUser(0)).isEqualTo("a")
             assertThat(tilesUser1).isEqualTo("b,c".toTileSpecs())
-        }
-
-    @Test
-    fun removeTiles() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-
-            storeTilesForUser("a,b", 0)
-
-            underTest.removeTiles(userId = 0, listOf(TileSpec.create("a")))
-
-            assertThat(loadTilesForUser(0)).isEqualTo("b")
-            assertThat(tiles).isEqualTo("b".toTileSpecs())
-        }
-
-    @Test
-    fun removeTilesNotThere_noop() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-
-            val specs = "a,b"
-            storeTilesForUser(specs, 0)
-
-            underTest.removeTiles(userId = 0, listOf(TileSpec.create("c")))
-
-            assertThat(loadTilesForUser(0)).isEqualTo(specs)
-            assertThat(tiles).isEqualTo(specs.toTileSpecs())
-        }
-
-    @Test
-    fun removeInvalidTile_noop() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-
-            val specs = "a,b"
-            storeTilesForUser(specs, 0)
-
-            underTest.removeTiles(userId = 0, listOf(TileSpec.Invalid))
-
-            assertThat(loadTilesForUser(0)).isEqualTo(specs)
-            assertThat(tiles).isEqualTo(specs.toTileSpecs())
+            assertThat(loadTilesForUser(1)).isEqualTo("b,c")
         }
 
     @Test
     fun removeTileFromSecondaryUser_removedOnlyInCorrectUser() =
         testScope.runTest {
-            val user0Tiles by collectLastValue(underTest.tilesSpecs(0))
-            val user1Tiles by collectLastValue(underTest.tilesSpecs(1))
-
             val specs = "a,b"
             storeTilesForUser(specs, 0)
             storeTilesForUser(specs, 1)
+            val user0Tiles by collectLastValue(underTest.tilesSpecs(0))
+            val user1Tiles by collectLastValue(underTest.tilesSpecs(1))
+            runCurrent()
 
             underTest.removeTiles(userId = 1, listOf(TileSpec.create("a")))
 
-            assertThat(loadTilesForUser(0)).isEqualTo(specs)
             assertThat(user0Tiles).isEqualTo(specs.toTileSpecs())
-            assertThat(loadTilesForUser(1)).isEqualTo("b")
+            assertThat(loadTilesForUser(0)).isEqualTo(specs)
             assertThat(user1Tiles).isEqualTo("b".toTileSpecs())
-        }
-
-    @Test
-    fun removeMultipleTiles() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-
-            storeTilesForUser("a,b,c,d", 0)
-
-            underTest.removeTiles(userId = 0, listOf(TileSpec.create("a"), TileSpec.create("c")))
-
-            assertThat(loadTilesForUser(0)).isEqualTo("b,d")
-            assertThat(tiles).isEqualTo("b,d".toTileSpecs())
-        }
-
-    @Test
-    fun changeTiles() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-
-            val specs = "a,custom(b/c)"
-
-            underTest.setTiles(userId = 0, specs.toTileSpecs())
-
-            assertThat(loadTilesForUser(0)).isEqualTo(specs)
-            assertThat(tiles).isEqualTo(specs.toTileSpecs())
-        }
-
-    @Test
-    fun changeTiles_ignoresInvalid() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-
-            val specs = "a,custom(b/c)"
-
-            underTest.setTiles(userId = 0, listOf(TileSpec.Invalid) + specs.toTileSpecs())
-
-            assertThat(loadTilesForUser(0)).isEqualTo(specs)
-            assertThat(tiles).isEqualTo(specs.toTileSpecs())
-        }
-
-    @Test
-    fun changeTiles_empty_noChanges() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-
-            underTest.setTiles(userId = 0, emptyList())
-
-            assertThat(loadTilesForUser(0)).isNull()
-            assertThat(tiles).isEqualTo(getDefaultTileSpecs())
+            assertThat(loadTilesForUser(1)).isEqualTo("b")
         }
 
     @Test
     fun changeTiles_forCorrectUser() =
         testScope.runTest {
-            val user0Tiles by collectLastValue(underTest.tilesSpecs(0))
-            val user1Tiles by collectLastValue(underTest.tilesSpecs(1))
-
             val specs = "a"
             storeTilesForUser(specs, 0)
             storeTilesForUser(specs, 1)
+            val user0Tiles by collectLastValue(underTest.tilesSpecs(0))
+            val user1Tiles by collectLastValue(underTest.tilesSpecs(1))
+            runCurrent()
 
             underTest.setTiles(userId = 1, "b".toTileSpecs())
 
-            assertThat(loadTilesForUser(0)).isEqualTo("a")
             assertThat(user0Tiles).isEqualTo(specs.toTileSpecs())
+            assertThat(loadTilesForUser(0)).isEqualTo("a")
 
-            assertThat(loadTilesForUser(1)).isEqualTo("b")
             assertThat(user1Tiles).isEqualTo("b".toTileSpecs())
-        }
-
-    @Test
-    fun multipleConcurrentRemovals_bothRemoved() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tilesSpecs(0))
-
-            val specs = "a,b,c"
-            storeTilesForUser(specs, 0)
-
-            coroutineScope {
-                underTest.removeTiles(userId = 0, listOf(TileSpec.create("c")))
-                underTest.removeTiles(userId = 0, listOf(TileSpec.create("a")))
-            }
-
-            assertThat(loadTilesForUser(0)).isEqualTo("b")
-            assertThat(tiles).isEqualTo("b".toTileSpecs())
+            assertThat(loadTilesForUser(1)).isEqualTo("b")
         }
 
     @Test
@@ -361,6 +170,7 @@
             retailModeRepository.setRetailMode(true)
 
             val tiles by collectLastValue(underTest.tilesSpecs(0))
+            runCurrent()
 
             assertThat(tiles).isEqualTo(RETAIL_TILES.toTileSpecs())
         }
@@ -369,25 +179,13 @@
     fun retailMode_cannotModifyTiles() =
         testScope.runTest {
             retailModeRepository.setRetailMode(true)
-
-            underTest.setTiles(0, DEFAULT_TILES.toTileSpecs())
-
-            assertThat(loadTilesForUser(0)).isNull()
-        }
-
-    @Test
-    fun emptyTilesReplacedByDefaultInSettings() =
-        testScope.runTest {
             val tiles by collectLastValue(underTest.tilesSpecs(0))
             runCurrent()
 
-            assertThat(loadTilesForUser(0))
-                .isEqualTo(getDefaultTileSpecs().map { it.spec }.joinToString(","))
-        }
+            underTest.setTiles(0, listOf(TileSpec.create("a")))
 
-    private fun getDefaultTileSpecs(): List<TileSpec> {
-        return QSHost.getDefaultSpecs(context.resources).map(TileSpec::create)
-    }
+            assertThat(loadTilesForUser(0)).isEqualTo(DEFAULT_TILES)
+        }
 
     private fun TestScope.storeTilesForUser(specs: String, forUser: Int) {
         secureSettings.putStringForUser(SETTING, specs, forUser)
@@ -403,8 +201,6 @@
         private const val RETAIL_TILES = "d"
         private const val SETTING = Settings.Secure.QS_TILES
 
-        private fun String.toTileSpecs(): List<TileSpec> {
-            return split(",").map(TileSpec::create)
-        }
+        private fun String.toTileSpecs() = TilesSettingConverter.toTilesList(this)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt
new file mode 100644
index 0000000..2087623
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt
@@ -0,0 +1,100 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RoboPilotTest
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TilesSettingConverterTest : SysuiTestCase() {
+
+    @Test
+    fun toTilesList_correctContentAndOrdering() {
+        val specString =
+            listOf(
+                    "c",
+                    "b",
+                    "custom(x/y)",
+                    "d",
+                )
+                .joinToString(DELIMITER)
+
+        val expected =
+            listOf(
+                TileSpec.create("c"),
+                TileSpec.create("b"),
+                TileSpec.create("custom(x/y)"),
+                TileSpec.create("d"),
+            )
+
+        assertThat(TilesSettingConverter.toTilesList(specString)).isEqualTo(expected)
+    }
+
+    @Test
+    fun toTilesList_removesInvalid() {
+        val specString =
+            listOf(
+                    "a",
+                    "",
+                    "b",
+                )
+                .joinToString(DELIMITER)
+        assertThat(TileSpec.create("")).isEqualTo(TileSpec.Invalid)
+        val expected =
+            listOf(
+                TileSpec.create("a"),
+                TileSpec.create("b"),
+            )
+        assertThat(TilesSettingConverter.toTilesList(specString)).isEqualTo(expected)
+    }
+
+    @Test
+    fun toTilesSet_correctContent() {
+        val specString =
+            listOf(
+                    "c",
+                    "b",
+                    "custom(x/y)",
+                    "d",
+                )
+                .joinToString(DELIMITER)
+
+        val expected =
+            setOf(
+                TileSpec.create("c"),
+                TileSpec.create("b"),
+                TileSpec.create("custom(x/y)"),
+                TileSpec.create("d"),
+            )
+
+        assertThat(TilesSettingConverter.toTilesSet(specString)).isEqualTo(expected)
+    }
+
+    @Test
+    fun toTilesSet_removesInvalid() {
+        val specString =
+            listOf(
+                    "a",
+                    "",
+                    "b",
+                )
+                .joinToString(DELIMITER)
+        assertThat(TileSpec.create("")).isEqualTo(TileSpec.Invalid)
+        val expected =
+            setOf(
+                TileSpec.create("a"),
+                TileSpec.create("b"),
+            )
+        assertThat(TilesSettingConverter.toTilesSet(specString)).isEqualTo(expected)
+    }
+
+    companion object {
+        private const val DELIMITER = ","
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt
new file mode 100644
index 0000000..81fd72b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt
@@ -0,0 +1,160 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RoboPilotTest
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UserAutoAddRepositoryTest : SysuiTestCase() {
+    private val secureSettings = FakeSettings()
+
+    @Mock private lateinit var logger: QSPipelineLogger
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private lateinit var underTest: UserAutoAddRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            UserAutoAddRepository(
+                USER,
+                secureSettings,
+                logger,
+                testScope.backgroundScope,
+                testDispatcher,
+            )
+    }
+
+    @Test
+    fun nonExistentSetting_emptySet() =
+        testScope.runTest {
+            val specs by collectLastValue(underTest.autoAdded())
+
+            assertThat(specs).isEmpty()
+        }
+
+    @Test
+    fun settingsChange_noChanges() =
+        testScope.runTest {
+            val value = "a,custom(b/c)"
+            store(value)
+            val specs by collectLastValue(underTest.autoAdded())
+            runCurrent()
+
+            assertThat(specs).isEqualTo(value.toTilesSet())
+
+            val newValue = "a"
+            store(newValue)
+
+            assertThat(specs).isEqualTo(value.toTilesSet())
+        }
+
+    @Test
+    fun noInvalidTileSpecs() =
+        testScope.runTest {
+            val specs = "d,custom(bad)"
+            store(specs)
+            val tiles by collectLastValue(underTest.autoAdded())
+            runCurrent()
+
+            assertThat(tiles).isEqualTo("d".toTilesSet())
+        }
+
+    @Test
+    fun markAdded() =
+        testScope.runTest {
+            val specs = mutableSetOf(TileSpec.create("a"))
+            val autoAdded by collectLastValue(underTest.autoAdded())
+            runCurrent()
+
+            underTest.markTileAdded(TileSpec.create("a"))
+
+            assertThat(autoAdded).containsExactlyElementsIn(specs)
+
+            specs.add(TileSpec.create("b"))
+            underTest.markTileAdded(TileSpec.create("b"))
+
+            assertThat(autoAdded).containsExactlyElementsIn(specs)
+        }
+
+    @Test
+    fun markAdded_Invalid_noop() =
+        testScope.runTest {
+            val autoAdded by collectLastValue(underTest.autoAdded())
+            runCurrent()
+
+            underTest.markTileAdded(TileSpec.Invalid)
+
+            Truth.assertThat(autoAdded).isEmpty()
+        }
+
+    @Test
+    fun unmarkAdded() =
+        testScope.runTest {
+            val specs = "a,custom(b/c)"
+            store(specs)
+            val autoAdded by collectLastValue(underTest.autoAdded())
+            runCurrent()
+
+            underTest.unmarkTileAdded(TileSpec.create("a"))
+
+            assertThat(autoAdded).containsExactlyElementsIn(setOf(TileSpec.create("custom(b/c)")))
+        }
+
+    @Test
+    fun restore_addsRestoredTiles() =
+        testScope.runTest {
+            val specs = "a,b"
+            val restored = "b,c"
+            store(specs)
+            val autoAdded by collectLastValue(underTest.autoAdded())
+            runCurrent()
+
+            val restoreData =
+                RestoreData(
+                    emptyList(),
+                    restored.toTilesSet(),
+                    USER,
+                )
+            underTest.reconcileRestore(restoreData)
+
+            assertThat(autoAdded).containsExactlyElementsIn("a,b,c".toTilesSet())
+        }
+
+    private fun store(specs: String) {
+        secureSettings.putStringForUser(SETTING, specs, USER)
+    }
+
+    companion object {
+        private const val USER = 10
+        private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES
+
+        private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
new file mode 100644
index 0000000..389580c1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
@@ -0,0 +1,351 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RoboPilotTest
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class UserTileSpecRepositoryTest : SysuiTestCase() {
+    private val secureSettings = FakeSettings()
+    private val defaultTilesRepository =
+        object : DefaultTilesRepository {
+            override val defaultTiles: List<TileSpec>
+                get() = DEFAULT_TILES.toTileSpecs()
+        }
+
+    @Mock private lateinit var logger: QSPipelineLogger
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private lateinit var underTest: UserTileSpecRepository
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            UserTileSpecRepository(
+                USER,
+                defaultTilesRepository,
+                secureSettings,
+                logger,
+                testScope.backgroundScope,
+                testDispatcher,
+            )
+    }
+
+    @Test
+    fun emptySetting_usesDefaultValue() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tiles())
+            assertThat(tiles).isEqualTo(getDefaultTileSpecs())
+        }
+
+    @Test
+    fun changeInSettings_valueDoesntChange() =
+        testScope.runTest {
+            storeTiles("a")
+            val tiles by collectLastValue(underTest.tiles())
+
+            assertThat(tiles).isEqualTo(listOf(TileSpec.create("a")))
+
+            storeTiles("a,custom(b/c)")
+            assertThat(tiles).isEqualTo(listOf(TileSpec.create("a")))
+        }
+
+    @Test
+    fun changeInSettings_settingIsRestored() =
+        testScope.runTest {
+            storeTiles("a")
+            val tiles by collectLastValue(underTest.tiles())
+            runCurrent()
+
+            storeTiles("a,custom(b/c)")
+            assertThat(loadTiles()).isEqualTo("a")
+        }
+
+    @Test
+    fun invalidTilesAreNotPresent() =
+        testScope.runTest {
+            val specs = "d,custom(bad)"
+            storeTiles(specs)
+
+            val tiles by collectLastValue(underTest.tiles())
+
+            assertThat(tiles).isEqualTo(specs.toTileSpecs().filter { it != TileSpec.Invalid })
+        }
+
+    @Test
+    fun noValidTiles_defaultSet() =
+        testScope.runTest {
+            storeTiles("custom(bad),custom()")
+
+            val tiles by collectLastValue(underTest.tiles())
+
+            assertThat(tiles).isEqualTo(getDefaultTileSpecs())
+        }
+
+    /*
+     * Following tests are for the possible actions that can be performed to the list of tiles.
+     * In general, the tests follow this scheme:
+     *
+     * 1. Set starting tiles in Settings
+     * 2. Start collection of flows
+     * 3. Call `runCurrent` so all collectors are started (side effects)
+     * 4. Perform operation
+     * 5. Check that the flow contains the right value
+     * 6. Check that settings contains the right value.
+     */
+
+    @Test
+    fun addTileAtEnd() =
+        testScope.runTest {
+            storeTiles("a")
+            val tiles by collectLastValue(underTest.tiles())
+            runCurrent()
+
+            underTest.addTile(TileSpec.create("b"))
+
+            val expected = "a,b"
+            assertThat(tiles).isEqualTo(expected.toTileSpecs())
+            assertThat(loadTiles()).isEqualTo(expected)
+        }
+
+    @Test
+    fun addTileAtPosition() =
+        testScope.runTest {
+            storeTiles("a,custom(b/c)")
+            val tiles by collectLastValue(underTest.tiles())
+            runCurrent()
+
+            underTest.addTile(TileSpec.create("d"), position = 1)
+
+            val expected = "a,d,custom(b/c)"
+            assertThat(tiles).isEqualTo(expected.toTileSpecs())
+            assertThat(loadTiles()).isEqualTo(expected)
+        }
+
+    @Test
+    fun addInvalidTile_noop() =
+        testScope.runTest {
+            val specs = "a,custom(b/c)"
+            storeTiles(specs)
+            val tiles by collectLastValue(underTest.tiles())
+            runCurrent()
+
+            underTest.addTile(TileSpec.Invalid)
+
+            assertThat(tiles).isEqualTo(specs.toTileSpecs())
+            assertThat(loadTiles()).isEqualTo(specs)
+        }
+
+    @Test
+    fun addTileAtPosition_tooLarge_addedAtEnd() =
+        testScope.runTest {
+            val specs = "a,custom(b/c)"
+            storeTiles(specs)
+            val tiles by collectLastValue(underTest.tiles())
+            runCurrent()
+
+            underTest.addTile(TileSpec.create("d"), position = 100)
+
+            val expected = "a,custom(b/c),d"
+            assertThat(tiles).isEqualTo(expected.toTileSpecs())
+            assertThat(loadTiles()).isEqualTo(expected)
+        }
+
+    @Test
+    fun removeTiles() =
+        testScope.runTest {
+            storeTiles("a,b")
+            val tiles by collectLastValue(underTest.tiles())
+            runCurrent()
+
+            underTest.removeTiles(listOf(TileSpec.create("a")))
+
+            assertThat(tiles).isEqualTo("b".toTileSpecs())
+            assertThat(loadTiles()).isEqualTo("b")
+        }
+
+    @Test
+    fun removeTilesNotThere_noop() =
+        testScope.runTest {
+            val specs = "a,b"
+            storeTiles(specs)
+            val tiles by collectLastValue(underTest.tiles())
+            runCurrent()
+
+            underTest.removeTiles(listOf(TileSpec.create("c")))
+
+            assertThat(tiles).isEqualTo(specs.toTileSpecs())
+            assertThat(loadTiles()).isEqualTo(specs)
+        }
+
+    @Test
+    fun removeInvalidTile_noop() =
+        testScope.runTest {
+            val specs = "a,b"
+            storeTiles(specs)
+            val tiles by collectLastValue(underTest.tiles())
+            runCurrent()
+
+            underTest.removeTiles(listOf(TileSpec.Invalid))
+
+            assertThat(tiles).isEqualTo(specs.toTileSpecs())
+            assertThat(loadTiles()).isEqualTo(specs)
+        }
+
+    @Test
+    fun removeMultipleTiles() =
+        testScope.runTest {
+            storeTiles("a,b,c,d")
+            val tiles by collectLastValue(underTest.tiles())
+            runCurrent()
+
+            underTest.removeTiles(listOf(TileSpec.create("a"), TileSpec.create("c")))
+
+            assertThat(tiles).isEqualTo("b,d".toTileSpecs())
+            assertThat(loadTiles()).isEqualTo("b,d")
+        }
+
+    @Test
+    fun changeTiles() =
+        testScope.runTest {
+            val specs = "a,custom(b/c)"
+            val tiles by collectLastValue(underTest.tiles())
+            runCurrent()
+
+            underTest.setTiles(specs.toTileSpecs())
+
+            assertThat(tiles).isEqualTo(specs.toTileSpecs())
+            assertThat(loadTiles()).isEqualTo(specs)
+        }
+
+    @Test
+    fun changeTiles_ignoresInvalid() =
+        testScope.runTest {
+            val specs = "a,custom(b/c)"
+            val tiles by collectLastValue(underTest.tiles())
+            runCurrent()
+
+            underTest.setTiles(listOf(TileSpec.Invalid) + specs.toTileSpecs())
+
+            assertThat(tiles).isEqualTo(specs.toTileSpecs())
+            assertThat(loadTiles()).isEqualTo(specs)
+        }
+
+    @Test
+    fun changeTiles_empty_noChanges() =
+        testScope.runTest {
+            val specs = "a,b,c,d"
+            storeTiles(specs)
+            val tiles by collectLastValue(underTest.tiles())
+            runCurrent()
+
+            underTest.setTiles(emptyList())
+
+            assertThat(tiles).isEqualTo(specs.toTileSpecs())
+            assertThat(loadTiles()).isEqualTo(specs)
+        }
+
+    @Test
+    fun multipleConcurrentRemovals_bothRemoved() =
+        testScope.runTest {
+            val specs = "a,b,c"
+            storeTiles(specs)
+            val tiles by collectLastValue(underTest.tiles())
+            runCurrent()
+
+            coroutineScope {
+                underTest.removeTiles(listOf(TileSpec.create("c")))
+                underTest.removeTiles(listOf(TileSpec.create("a")))
+            }
+
+            assertThat(tiles).isEqualTo("b".toTileSpecs())
+            assertThat(loadTiles()).isEqualTo("b")
+        }
+
+    @Test
+    fun emptyTilesReplacedByDefaultInSettings() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tiles())
+            runCurrent()
+
+            assertThat(loadTiles())
+                .isEqualTo(getDefaultTileSpecs().map { it.spec }.joinToString(","))
+        }
+
+    @Test
+    fun restoreDataIsProperlyReconciled() =
+        testScope.runTest {
+            // Tile b was just auto-added, so we should re-add it in position 1
+            // Tile e was auto-added before, but the user had removed it (not in the restored set).
+            // It should not be re-added
+            val specsBeforeRestore = "a,b,c,d,e"
+            val restoredSpecs = "a,c,d,f"
+            val autoAddedBeforeRestore = "b,d"
+            val restoredAutoAdded = "d,e"
+
+            storeTiles(specsBeforeRestore)
+            val tiles by collectLastValue(underTest.tiles())
+            runCurrent()
+
+            val restoreData =
+                RestoreData(
+                    restoredSpecs.toTileSpecs(),
+                    restoredAutoAdded.toTilesSet(),
+                    USER,
+                )
+            underTest.reconcileRestore(restoreData, autoAddedBeforeRestore.toTilesSet())
+            runCurrent()
+
+            val expected = "a,b,c,d,f"
+            assertThat(tiles).isEqualTo(expected.toTileSpecs())
+            assertThat(loadTiles()).isEqualTo(expected)
+        }
+
+    private fun getDefaultTileSpecs(): List<TileSpec> {
+        return defaultTilesRepository.defaultTiles
+    }
+
+    private fun TestScope.storeTiles(specs: String) {
+        secureSettings.putStringForUser(SETTING, specs, USER)
+        runCurrent()
+    }
+
+    private fun loadTiles(): String? {
+        return secureSettings.getStringForUser(SETTING, USER)
+    }
+
+    companion object {
+        private const val USER = 10
+        private const val DEFAULT_TILES = "a,b,c"
+        private const val SETTING = Settings.Secure.QS_TILES
+
+        private fun String.toTileSpecs() = TilesSettingConverter.toTilesList(this)
+        private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index dc1b9c4..a750524 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.qs.tiles.di.NewQSTileFactory
 import com.android.systemui.qs.toProto
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.user.data.repository.FakeUserRepository
@@ -91,6 +92,8 @@
 
     @Mock private lateinit var logger: QSPipelineLogger
 
+    @Mock private lateinit var newQSTileFactory: NewQSTileFactory
+
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
@@ -105,6 +108,8 @@
 
         featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
         featureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, true)
+        // TODO(b/299909337): Add test checking the new factory is used when the flag is on
+        featureFlags.set(Flags.QS_PIPELINE_NEW_TILES, true)
 
         userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
 
@@ -117,6 +122,7 @@
                 userRepository = userRepository,
                 customTileStatePersister = customTileStatePersister,
                 tileFactory = tileFactory,
+                newQSTileFactory = { newQSTileFactory },
                 customTileAddedRepository = customTileAddedRepository,
                 tileLifecycleManagerFactory = tileLifecycleManagerFactory,
                 userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
new file mode 100644
index 0000000..2e6b50b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
@@ -0,0 +1,94 @@
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.repository.FakeAutoAddRepository
+import com.android.systemui.qs.pipeline.data.repository.FakeQSSettingsRestoredRepository
+import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
+import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class RestoreReconciliationInteractorTest : SysuiTestCase() {
+
+    private val tileSpecRepository = FakeTileSpecRepository()
+    private val autoAddRepository = FakeAutoAddRepository()
+
+    private val qsSettingsRestoredRepository = FakeQSSettingsRestoredRepository()
+
+    private lateinit var underTest: RestoreReconciliationInteractor
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            RestoreReconciliationInteractor(
+                tileSpecRepository,
+                autoAddRepository,
+                qsSettingsRestoredRepository,
+                testScope.backgroundScope,
+                testDispatcher
+            )
+        underTest.start()
+    }
+
+    @Test
+    fun reconciliationInCorrectOrder_hascurrentAutoAdded() =
+        testScope.runTest {
+            val user = 10
+            val tiles by collectLastValue(tileSpecRepository.tilesSpecs(user))
+            val autoAdd by collectLastValue(autoAddRepository.autoAddedTiles(user))
+
+            // Tile b was just auto-added, so we should re-add it in position 1
+            // Tile e was auto-added before, but the user had removed it (not in the restored set).
+            // It should not be re-added
+            val specsBeforeRestore = "a,b,c,d,e"
+            val restoredSpecs = "a,c,d,f"
+            val autoAddedBeforeRestore = "b,d"
+            val restoredAutoAdded = "d,e"
+
+            val restoreData =
+                RestoreData(
+                    restoredSpecs.toTilesList(),
+                    restoredAutoAdded.toTilesSet(),
+                    user,
+                )
+
+            autoAddedBeforeRestore.toTilesSet().forEach {
+                autoAddRepository.markTileAdded(user, it)
+            }
+            tileSpecRepository.setTiles(user, specsBeforeRestore.toTilesList())
+
+            qsSettingsRestoredRepository.onDataRestored(restoreData)
+            runCurrent()
+
+            val expectedTiles = "a,b,c,d,f"
+            assertThat(tiles).isEqualTo(expectedTiles.toTilesList())
+
+            val expectedAutoAdd = "b,d,e"
+            assertThat(autoAdd).isEqualTo(expectedAutoAdd.toTilesSet())
+        }
+
+    companion object {
+        private fun String.toTilesList() = TilesSettingConverter.toTilesList(this)
+        private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
index 47b4244..077c813 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
@@ -6,7 +6,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserActionHandler
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -34,7 +33,7 @@
     fun testPassesIntentToStarter() {
         val intent = Intent("test.ACTION")
 
-        underTest.handle(QSTileUserAction.Click(context, null), intent)
+        underTest.handle(null, intent)
 
         verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
index 643866e..eacb080 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
@@ -1,11 +1,14 @@
 package com.android.systemui.qs.tiles.viewmodel
 
-import android.graphics.drawable.Icon
+import android.graphics.drawable.ShapeDrawable
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.MediumTest
+import com.android.internal.logging.InstanceId
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
 import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
@@ -71,20 +74,21 @@
                 fakeQSTileUserActionInteractor,
                 fakeQSTileDataInteractor,
                 object : QSTileDataToStateMapper<Any> {
-                    override fun map(config: QSTileConfig, data: Any): QSTileState {
-                        return QSTileState(config.tileIcon, config.tileLabel)
-                    }
+                    override fun map(config: QSTileConfig, data: Any): QSTileState =
+                        QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
                 },
                 testCoroutineDispatcher,
                 tileScope = scope.backgroundScope,
             ) {}
 
     private companion object {
+
         val TEST_QS_TILE_CONFIG =
             QSTileConfig(
                 TileSpec.create("default"),
-                Icon.createWithContentUri(""),
-                "",
+                Icon.Loaded(ShapeDrawable(), null),
+                0,
+                InstanceId.fakeInstanceId(0),
             )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 59b5953..a2aed98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -41,7 +41,7 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.MediaProjectionCaptureTarget;
+import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index d470d24..3ae1f35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -22,11 +22,13 @@
 import android.view.View
 import android.widget.Spinner
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
+import com.android.systemui.mediaprojection.permission.SINGLE_APP
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
@@ -35,8 +37,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
new file mode 100644
index 0000000..091531e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.systemui.screenshot
+
+import android.media.MediaPlayer
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import java.lang.IllegalStateException
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+class ScreenshotSoundControllerTest : SysuiTestCase() {
+
+    private val soundProvider = mock<ScreenshotSoundProvider>()
+    private val mediaPlayer = mock<MediaPlayer>()
+    private val bgDispatcher = UnconfinedTestDispatcher()
+    private val scope = TestScope(bgDispatcher)
+    @Before
+    fun setup() {
+        whenever(soundProvider.getScreenshotSound()).thenReturn(mediaPlayer)
+    }
+
+    @Test
+    fun init_soundLoading() {
+        createController()
+        bgDispatcher.scheduler.runCurrent()
+
+        verify(soundProvider).getScreenshotSound()
+    }
+
+    @Test
+    fun init_soundLoadingException_playAndReleaseDoNotThrow() = runTest {
+        whenever(soundProvider.getScreenshotSound()).thenThrow(IllegalStateException())
+
+        val controller = createController()
+
+        controller.playCameraSound().await()
+        controller.releaseScreenshotSound().await()
+
+        verify(mediaPlayer, never()).start()
+        verify(mediaPlayer, never()).release()
+    }
+
+    @Test
+    fun playCameraSound_soundLoadingSuccessful_mediaPlayerPlays() = runTest {
+        val controller = createController()
+
+        controller.playCameraSound().await()
+
+        verify(mediaPlayer).start()
+    }
+
+    @Test
+    fun playCameraSound_soundLoadingSuccessful_mediaPlayerReleases() = runTest {
+        val controller = createController()
+
+        controller.releaseScreenshotSound().await()
+
+        verify(mediaPlayer).release()
+    }
+
+    private fun createController() =
+        ScreenshotSoundControllerImpl(soundProvider, scope, bgDispatcher)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 31c8a3d7..ed731dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -118,7 +118,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.qs.QSFragment;
+import com.android.systemui.qs.QSFragmentLegacy;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.shade.data.repository.ShadeRepository;
@@ -291,7 +291,7 @@
     @Mock protected UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     @Mock protected ShadeTransitionController mShadeTransitionController;
     @Mock protected QS mQs;
-    @Mock protected QSFragment mQSFragment;
+    @Mock protected QSFragmentLegacy mQSFragment;
     @Mock protected ViewGroup mQsHeader;
     @Mock protected ViewParent mViewParent;
     @Mock protected ViewTreeObserver mViewTreeObserver;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt
index f7d2497..0c3af03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt
@@ -22,9 +22,9 @@
 import android.widget.FrameLayout
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.qs.QSFragment
+import com.android.systemui.qs.QSFragmentLegacy
+import com.android.systemui.res.R
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -40,7 +40,7 @@
     @Mock private lateinit var qsFrame: View
     @Mock private lateinit var stackScroller: View
     @Mock private lateinit var keyguardStatusBar: View
-    @Mock private lateinit var qsFragment: QSFragment
+    @Mock private lateinit var qsFragment: QSFragmentLegacy
 
     private lateinit var qsView: ViewGroup
     private lateinit var qsContainer: View
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index fb0d4db..8138b32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -34,7 +34,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.dump.DumpManager;
@@ -46,7 +45,8 @@
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.qs.QSFragment;
+import com.android.systemui.qs.QSFragmentLegacy;
+import com.android.systemui.res.R;
 import com.android.systemui.scene.SceneTestUtils;
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
 import com.android.systemui.screenrecord.RecordingController;
@@ -75,18 +75,17 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.user.domain.interactor.UserInteractor;
 import com.android.systemui.util.kotlin.JavaAdapter;
 
-import dagger.Lazy;
-
 import org.junit.After;
 import org.junit.Before;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import dagger.Lazy;
 import kotlinx.coroutines.test.TestScope;
 
 public class QuickSettingsControllerBaseTest extends SysuiTestCase {
@@ -109,7 +108,7 @@
     @Mock protected KeyguardBottomAreaView mQsFrame;
     @Mock protected KeyguardStatusBarView mKeyguardStatusBar;
     @Mock protected QS mQs;
-    @Mock protected QSFragment mQSFragment;
+    @Mock protected QSFragmentLegacy mQSFragment;
     @Mock protected Lazy<NotificationPanelViewController> mPanelViewControllerLazy;
     @Mock protected NotificationPanelViewController mNotificationPanelViewController;
     @Mock protected NotificationPanelView mPanelView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 48665fe..e714736 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.shared.clocks
 
+import android.content.ComponentName
 import android.content.ContentResolver
 import android.content.Context
 import android.graphics.drawable.Drawable
@@ -28,12 +29,11 @@
 import com.android.systemui.plugins.ClockMetadata
 import com.android.systemui.plugins.ClockProviderPlugin
 import com.android.systemui.plugins.ClockSettings
-import com.android.systemui.plugins.PluginListener
 import com.android.systemui.plugins.PluginLifecycleManager
+import com.android.systemui.plugins.PluginListener
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.fail
 import kotlinx.coroutines.CoroutineDispatcher
@@ -46,6 +46,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
@@ -66,7 +67,6 @@
     @Mock private lateinit var mockDefaultClock: ClockController
     @Mock private lateinit var mockThumbnail: Drawable
     @Mock private lateinit var mockContentResolver: ContentResolver
-    @Mock private lateinit var mockPluginLifecycle: PluginLifecycleManager<ClockProviderPlugin>
     private lateinit var fakeDefaultProvider: FakeClockPlugin
     private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
     private lateinit var registry: ClockRegistry
@@ -84,6 +84,41 @@
         }
     }
 
+    private class FakeLifecycle(
+        private val tag: String,
+        private val plugin: ClockProviderPlugin?,
+    ) : PluginLifecycleManager<ClockProviderPlugin> {
+        var onLoad: (() -> Unit)? = null
+        var onUnload: (() -> Unit)? = null
+
+        private var mIsLoaded: Boolean = true
+        override fun isLoaded() = mIsLoaded
+        override fun getPlugin(): ClockProviderPlugin? = if (isLoaded) plugin else null
+
+        var mComponentName = ComponentName("Package[$tag]", "Class[$tag]")
+        override fun toString() = "Manager[$tag]"
+        override fun getPackage(): String = mComponentName.getPackageName()
+        override fun getComponentName(): ComponentName = mComponentName
+
+        private var isDebug: Boolean = false
+        override fun getIsDebug(): Boolean = isDebug
+        override fun setIsDebug(value: Boolean) { isDebug = value }
+
+        override fun loadPlugin() {
+            if (!mIsLoaded) {
+                mIsLoaded = true
+                onLoad?.invoke()
+            }
+        }
+
+        override fun unloadPlugin() {
+            if (mIsLoaded) {
+                mIsLoaded = false
+                onUnload?.invoke()
+            }
+        }
+    }
+
     private class FakeClockPlugin : ClockProviderPlugin {
         private val metadata = mutableListOf<ClockMetadata>()
         private val createCallbacks = mutableMapOf<ClockId, (ClockId) -> ClockController>()
@@ -150,13 +185,15 @@
         val plugin1 = FakeClockPlugin()
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
+        val lifecycle1 = FakeLifecycle("1", plugin1)
 
         val plugin2 = FakeClockPlugin()
             .addClock("clock_3", "clock 3")
             .addClock("clock_4", "clock 4")
+        val lifecycle2 = FakeLifecycle("2", plugin2)
 
-        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle)
-        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle)
+        pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1)
+        pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2)
         val list = registry.getClocks()
         assertEquals(
             list.toSet(),
@@ -178,18 +215,18 @@
 
     @Test
     fun clockIdConflict_ErrorWithoutCrash_unloadDuplicate() {
-        val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
         val plugin1 = FakeClockPlugin()
             .addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail })
             .addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail })
+        val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
-        val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
         val plugin2 = FakeClockPlugin()
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
+        val lifecycle2 = spy(FakeLifecycle("2", plugin2))
 
-        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1)
-        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2)
+        pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1)
+        pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2)
         val list = registry.getClocks()
         assertEquals(
             list.toSet(),
@@ -204,8 +241,8 @@
         assertEquals(registry.createExampleClock("clock_2"), mockClock)
         assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail)
         assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail)
-        verify(mockPluginLifecycle1, never()).unloadPlugin()
-        verify(mockPluginLifecycle2, times(2)).unloadPlugin()
+        verify(lifecycle1, never()).unloadPlugin()
+        verify(lifecycle2, times(2)).unloadPlugin()
     }
 
     @Test
@@ -213,14 +250,16 @@
         val plugin1 = FakeClockPlugin()
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
+        val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
         val plugin2 = FakeClockPlugin()
             .addClock("clock_3", "clock 3", { mockClock })
             .addClock("clock_4", "clock 4")
+        val lifecycle2 = spy(FakeLifecycle("2", plugin2))
 
         registry.applySettings(ClockSettings("clock_3", null))
-        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle)
-        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle)
+        pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1)
+        pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2)
 
         val clock = registry.createCurrentClock()
         assertEquals(mockClock, clock)
@@ -231,17 +270,19 @@
         val plugin1 = FakeClockPlugin()
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
+        val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
         val plugin2 = FakeClockPlugin()
             .addClock("clock_3", "clock 3", { mockClock })
             .addClock("clock_4", "clock 4")
+        val lifecycle2 = spy(FakeLifecycle("2", plugin2))
 
         registry.applySettings(ClockSettings("clock_3", null))
 
-        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle)
+        pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1)
         assertEquals(DEFAULT_CLOCK_ID, registry.activeClockId)
 
-        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle)
+        pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2)
         assertEquals("clock_3", registry.activeClockId)
     }
 
@@ -250,15 +291,17 @@
         val plugin1 = FakeClockPlugin()
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
+        val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
         val plugin2 = FakeClockPlugin()
             .addClock("clock_3", "clock 3")
             .addClock("clock_4", "clock 4")
+        val lifecycle2 = spy(FakeLifecycle("2", plugin2))
 
         registry.applySettings(ClockSettings("clock_3", null))
-        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle)
-        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle)
-        pluginListener.onPluginUnloaded(plugin2, mockPluginLifecycle)
+        pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1)
+        pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2)
+        pluginListener.onPluginUnloaded(plugin2, lifecycle2)
 
         val clock = registry.createCurrentClock()
         assertEquals(clock, mockDefaultClock)
@@ -266,15 +309,15 @@
 
     @Test
     fun pluginRemoved_clockAndListChanged() {
-        val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
         val plugin1 = FakeClockPlugin()
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
+        val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
-        val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
         val plugin2 = FakeClockPlugin()
             .addClock("clock_3", "clock 3", { mockClock })
             .addClock("clock_4", "clock 4")
+        val lifecycle2 = spy(FakeLifecycle("2", plugin2))
 
         var changeCallCount = 0
         var listChangeCallCount = 0
@@ -288,32 +331,32 @@
         assertEquals(1, changeCallCount)
         assertEquals(0, listChangeCallCount)
 
-        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1)
+        pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1)
         scheduler.runCurrent()
         assertEquals(1, changeCallCount)
         assertEquals(1, listChangeCallCount)
 
-        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2)
+        pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2)
         scheduler.runCurrent()
         assertEquals(2, changeCallCount)
         assertEquals(2, listChangeCallCount)
 
-        pluginListener.onPluginUnloaded(plugin1, mockPluginLifecycle1)
+        pluginListener.onPluginUnloaded(plugin1, lifecycle1)
         scheduler.runCurrent()
         assertEquals(2, changeCallCount)
         assertEquals(2, listChangeCallCount)
 
-        pluginListener.onPluginUnloaded(plugin2, mockPluginLifecycle2)
+        pluginListener.onPluginUnloaded(plugin2, lifecycle2)
         scheduler.runCurrent()
         assertEquals(3, changeCallCount)
         assertEquals(2, listChangeCallCount)
 
-        pluginListener.onPluginDetached(mockPluginLifecycle1)
+        pluginListener.onPluginDetached(lifecycle1)
         scheduler.runCurrent()
         assertEquals(3, changeCallCount)
         assertEquals(3, listChangeCallCount)
 
-        pluginListener.onPluginDetached(mockPluginLifecycle2)
+        pluginListener.onPluginDetached(lifecycle2)
         scheduler.runCurrent()
         assertEquals(3, changeCallCount)
         assertEquals(4, listChangeCallCount)
@@ -321,8 +364,9 @@
 
     @Test
     fun unknownPluginAttached_clockAndListUnchanged_loadRequested() {
-        val mockPluginLifecycle = mock<PluginLifecycleManager<ClockProviderPlugin>>()
-        whenever(mockPluginLifecycle.getPackage()).thenReturn("some.other.package")
+        val lifecycle = FakeLifecycle("", null).apply {
+            mComponentName = ComponentName("some.other.package", "SomeClass")
+        }
 
         var changeCallCount = 0
         var listChangeCallCount = 0
@@ -331,7 +375,7 @@
             override fun onAvailableClocksChanged() { listChangeCallCount++ }
         })
 
-        assertEquals(true, pluginListener.onPluginAttached(mockPluginLifecycle))
+        assertEquals(true, pluginListener.onPluginAttached(lifecycle))
         scheduler.runCurrent()
         assertEquals(0, changeCallCount)
         assertEquals(0, listChangeCallCount)
@@ -339,10 +383,12 @@
 
     @Test
     fun knownPluginAttached_clockAndListChanged_notLoaded() {
-        val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
-        whenever(mockPluginLifecycle1.getPackage()).thenReturn("com.android.systemui.clocks.metro")
-        val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
-        whenever(mockPluginLifecycle2.getPackage()).thenReturn("com.android.systemui.clocks.bignum")
+        val lifecycle1 = FakeLifecycle("Metro", null).apply {
+            mComponentName = ComponentName("com.android.systemui.clocks.metro", "MetroClock")
+        }
+        val lifecycle2 = FakeLifecycle("BigNum", null).apply {
+            mComponentName = ComponentName("com.android.systemui.clocks.bignum", "BigNumClock")
+        }
 
         var changeCallCount = 0
         var listChangeCallCount = 0
@@ -356,12 +402,12 @@
         assertEquals(1, changeCallCount)
         assertEquals(0, listChangeCallCount)
 
-        assertEquals(false, pluginListener.onPluginAttached(mockPluginLifecycle1))
+        assertEquals(false, pluginListener.onPluginAttached(lifecycle1))
         scheduler.runCurrent()
         assertEquals(1, changeCallCount)
         assertEquals(1, listChangeCallCount)
 
-        assertEquals(false, pluginListener.onPluginAttached(mockPluginLifecycle2))
+        assertEquals(false, pluginListener.onPluginAttached(lifecycle2))
         scheduler.runCurrent()
         assertEquals(1, changeCallCount)
         assertEquals(2, listChangeCallCount)
@@ -369,18 +415,14 @@
 
     @Test
     fun pluginAddRemove_concurrentModification() {
-        val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
-        val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
-        val mockPluginLifecycle3 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
-        val mockPluginLifecycle4 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
         val plugin1 = FakeClockPlugin().addClock("clock_1", "clock 1")
+        val lifecycle1 = FakeLifecycle("1", plugin1)
         val plugin2 = FakeClockPlugin().addClock("clock_2", "clock 2")
+        val lifecycle2 = FakeLifecycle("2", plugin2)
         val plugin3 = FakeClockPlugin().addClock("clock_3", "clock 3")
+        val lifecycle3 = FakeLifecycle("3", plugin3)
         val plugin4 = FakeClockPlugin().addClock("clock_4", "clock 4")
-        whenever(mockPluginLifecycle1.isLoaded).thenReturn(true)
-        whenever(mockPluginLifecycle2.isLoaded).thenReturn(true)
-        whenever(mockPluginLifecycle3.isLoaded).thenReturn(true)
-        whenever(mockPluginLifecycle4.isLoaded).thenReturn(true)
+        val lifecycle4 = FakeLifecycle("4", plugin4)
 
         // Set the current clock to the final clock to load
         registry.applySettings(ClockSettings("clock_4", null))
@@ -390,15 +432,15 @@
         // unload other plugins. This causes ClockRegistry to modify the list of available clock
         // plugins while it is being iterated over. In production this happens as a result of a
         // thread race, instead of synchronously like it does here.
-        whenever(mockPluginLifecycle2.unloadPlugin()).then {
-            pluginListener.onPluginDetached(mockPluginLifecycle1)
-            pluginListener.onPluginLoaded(plugin4, mockContext, mockPluginLifecycle4)
+        lifecycle2.onUnload = {
+            pluginListener.onPluginDetached(lifecycle1)
+            pluginListener.onPluginLoaded(plugin4, mockContext, lifecycle4)
         }
 
         // Load initial plugins
-        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1)
-        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2)
-        pluginListener.onPluginLoaded(plugin3, mockContext, mockPluginLifecycle3)
+        pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1)
+        pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2)
+        pluginListener.onPluginLoaded(plugin3, mockContext, lifecycle3)
 
         // Repeatedly verify the loaded providers to get final state
         registry.verifyLoadedProviders()
@@ -484,11 +526,11 @@
     private fun testTransitClockFlag(flag: Boolean) {
         featureFlags.set(TRANSIT_CLOCK, flag)
         registry.isTransitClockEnabled = featureFlags.isEnabled(TRANSIT_CLOCK)
-        val mockPluginLifecycle = mock<PluginLifecycleManager<ClockProviderPlugin>>()
         val plugin = FakeClockPlugin()
                 .addClock("clock_1", "clock 1")
                 .addClock("DIGITAL_CLOCK_METRO", "metro clock")
-        pluginListener.onPluginLoaded(plugin, mockContext, mockPluginLifecycle)
+        val lifecycle = FakeLifecycle("metro", plugin)
+        pluginListener.onPluginLoaded(plugin, mockContext, lifecycle)
 
         val list = registry.getClocks()
         if (flag) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 1592c30..a6180ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -248,7 +248,7 @@
     }
 
     @Test
-    public void testUpdateIconScale_smallerFontAndConstrainedDrawableSizeLessThanDpIconSize() {
+    public void testUpdateIconScale_smallerFontAndRawDrawableSizeLessThanDpIconSize() {
         int dpIconSize = 60;
         int dpDrawingSize = 30;
         // smaller font scaling causes the spIconSize < dpIconSize
@@ -262,12 +262,42 @@
         setIconDrawableWithSize(/* width= */ 50, /* height= */ 50);
         mIconView.maybeUpdateIconScaleDimens();
 
-        // WHEN both the constrained drawable width/height are less than dpIconSize,
+        // WHEN both the raw/constrained drawable width/height are less than dpIconSize,
+        // THEN the icon is scaled up from constrained drawable size to the raw drawable size
+        float scaleToBackRawDrawableSize = (float) 50 / 40;
         // THEN the icon is scaled down from dpIconSize to fit the dpDrawingSize
         float scaleToFitDrawingSize = (float) dpDrawingSize / dpIconSize;
         // THEN the scaled icon should be scaled down further to fit spIconSize
         float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
-        assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize, mIconView.getIconScale(), 0.01f);
+        assertEquals(scaleToBackRawDrawableSize * scaleToFitDrawingSize * scaleToFitSpIconSize,
+                mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_smallerFontAndConstrainedDrawableSizeLessThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // smaller font scaling causes the spIconSize < dpIconSize
+        int spIconSize = 40;
+        // the icon view layout size would be 40x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 70x70. When put the drawable into iconView whose
+        // layout size is 40x150, the drawable size would be constrained to 40x40
+        setIconDrawableWithSize(/* width= */ 70, /* height= */ 70);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN the raw drawable width/height are larger than dpIconSize,
+        //      but the constrained drawable width/height are less than dpIconSize,
+        // THEN the icon is scaled up from constrained drawable size to fit dpIconSize
+        float scaleToFitDpIconSize = (float) dpIconSize / 40;
+        // THEN the icon is scaled down from dpIconSize to fit the dpDrawingSize
+        float scaleToFitDrawingSize = (float) dpDrawingSize / dpIconSize;
+        // THEN the scaled icon should be scaled down further to fit spIconSize
+        float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+        assertEquals(scaleToFitDpIconSize * scaleToFitDrawingSize * scaleToFitSpIconSize,
+                mIconView.getIconScale(), 0.01f);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt
new file mode 100644
index 0000000..8c3bfd5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt
@@ -0,0 +1,254 @@
+/*
+ * 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.systemui.statusbar.notification.row.wrapper
+
+import android.app.PendingIntent
+import android.app.PendingIntent.CancelListener
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationTemplateViewWrapper.ActionPendingIntentCancellationHandler
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationTemplateViewWrapperTest : SysuiTestCase() {
+
+    private lateinit var helper: NotificationTestHelper
+
+    private lateinit var root: ViewGroup
+    private lateinit var view: ViewGroup
+    private lateinit var row: ExpandableNotificationRow
+    private lateinit var actions: ViewGroup
+
+    private lateinit var looper: TestableLooper
+
+    @Before
+    fun setUp() {
+        looper = TestableLooper.get(this)
+        allowTestableLooperAsMainThread()
+        helper = NotificationTestHelper(mContext, mDependency, looper)
+        row = helper.createRow()
+        // Some code in the view iterates through parents so we need some extra containers around
+        // it.
+        root = FrameLayout(mContext)
+        val root2 = FrameLayout(mContext)
+        root.addView(root2)
+        view =
+            (LayoutInflater.from(mContext)
+                .inflate(R.layout.notification_template_material_big_text, root2) as ViewGroup)
+        actions = view.findViewById(R.id.actions)!!
+        ViewUtils.attachView(root)
+    }
+
+    @Test
+    fun noActionsPresent_noCrash() {
+        view.removeView(actions)
+        val wrapper = NotificationTemplateViewWrapper(mContext, view, row)
+        wrapper.onContentUpdated(row)
+    }
+
+    @Test
+    fun actionPendingIntentCancelled_actionDisabled() {
+        val wrapper = NotificationTemplateViewWrapper(mContext, view, row)
+        val action1 = createActionWithPendingIntent()
+        val action2 = createActionWithPendingIntent()
+        val action3 = createActionWithPendingIntent()
+        wrapper.onContentUpdated(row)
+        waitForUiOffloadThread() // Wait for cancellation registration to execute.
+
+        val pi3 = getPendingIntent(action3)
+        pi3.cancel()
+        looper.processAllMessages() // Wait for listener callbacks to execute
+
+        assertThat(action1.isEnabled).isTrue()
+        assertThat(action2.isEnabled).isTrue()
+        assertThat(action3.isEnabled).isFalse()
+        assertThat(wrapper.mCancelledPendingIntents)
+            .doesNotContain(getPendingIntent(action1).hashCode())
+        assertThat(wrapper.mCancelledPendingIntents)
+            .doesNotContain(getPendingIntent(action2).hashCode())
+        assertThat(wrapper.mCancelledPendingIntents).contains(pi3.hashCode())
+    }
+
+    @Test
+    fun newActionWithSamePendingIntentPosted_actionDisabled() {
+        val wrapper = NotificationTemplateViewWrapper(mContext, view, row)
+        val action = createActionWithPendingIntent()
+        wrapper.onContentUpdated(row)
+        waitForUiOffloadThread() // Wait for cancellation registration to execute.
+
+        // Cancel the intent and check action is now false.
+        val pi = getPendingIntent(action)
+        pi.cancel()
+        looper.processAllMessages() // Wait for listener callbacks to execute
+        assertThat(action.isEnabled).isFalse()
+
+        // Create a NEW action and make sure that one will also be cancelled with same PI.
+        actions.removeView(action)
+        val newAction = createActionWithPendingIntent(pi)
+        wrapper.onContentUpdated(row)
+        looper.processAllMessages() // Wait for listener callbacks to execute
+
+        assertThat(newAction.isEnabled).isFalse()
+        assertThat(wrapper.mCancelledPendingIntents).containsExactly(pi.hashCode())
+    }
+
+    @Test
+    fun twoActionsWithSameCancelledIntent_bothActionsDisabled() {
+        val wrapper = NotificationTemplateViewWrapper(mContext, view, row)
+        val action1 = createActionWithPendingIntent()
+        val action2 = createActionWithPendingIntent()
+        val action3 = createActionWithPendingIntent(getPendingIntent(action2))
+        wrapper.onContentUpdated(row)
+        waitForUiOffloadThread() // Wait for cancellation registration to execute.
+
+        val pi = getPendingIntent(action2)
+        pi.cancel()
+        looper.processAllMessages() // Wait for listener callbacks to execute
+
+        assertThat(action1.isEnabled).isTrue()
+        assertThat(action2.isEnabled).isFalse()
+        assertThat(action3.isEnabled).isFalse()
+    }
+
+    @Test
+    fun actionPendingIntentCancelled_whileDetached_actionDisabled() {
+        ViewUtils.detachView(root)
+        val wrapper = NotificationTemplateViewWrapper(mContext, view, row)
+        val action = createActionWithPendingIntent()
+        wrapper.onContentUpdated(row)
+        getPendingIntent(action).cancel()
+        ViewUtils.attachView(root)
+        waitForUiOffloadThread()
+        looper.processAllMessages()
+
+        assertThat(action.isEnabled).isFalse()
+    }
+
+    @Test
+    fun actionViewDetached_pendingIntentListenersDeregistered() {
+        val pi =
+            PendingIntent.getActivity(
+                mContext,
+                System.currentTimeMillis().toInt(),
+                Intent(Intent.ACTION_VIEW),
+                PendingIntent.FLAG_IMMUTABLE
+            )
+        val spy = Mockito.spy(pi)
+        createActionWithPendingIntent(spy)
+        val wrapper = NotificationTemplateViewWrapper(mContext, view, row)
+        wrapper.onContentUpdated(row)
+        ViewUtils.detachView(root)
+        waitForUiOffloadThread()
+        looper.processAllMessages()
+
+        val captor = ArgumentCaptor.forClass(CancelListener::class.java)
+        verify(spy, times(1)).registerCancelListener(captor.capture())
+        verify(spy, times(1)).unregisterCancelListener(captor.value)
+    }
+
+    @Test
+    fun actionViewUpdated_oldPendingIntentListenersRemoved() {
+        val pi =
+            PendingIntent.getActivity(
+                mContext,
+                System.currentTimeMillis().toInt(),
+                Intent(Intent.ACTION_VIEW),
+                PendingIntent.FLAG_IMMUTABLE
+            )
+        val spy = Mockito.spy(pi)
+        val action = createActionWithPendingIntent(spy)
+        val wrapper = NotificationTemplateViewWrapper(mContext, view, row)
+        wrapper.onContentUpdated(row)
+        waitForUiOffloadThread()
+        looper.processAllMessages()
+
+        // Grab set attach listener
+        val attachListener =
+            Mockito.spy(action.getTag(com.android.systemui.res.R.id.pending_intent_listener_tag))
+                as ActionPendingIntentCancellationHandler
+        action.setTag(com.android.systemui.res.R.id.pending_intent_listener_tag, attachListener)
+
+        // Update pending intent in the existing action
+        val newPi =
+            PendingIntent.getActivity(
+                mContext,
+                System.currentTimeMillis().toInt(),
+                Intent(Intent.ACTION_ALARM_CHANGED),
+                PendingIntent.FLAG_IMMUTABLE
+            )
+        action.setTagInternal(R.id.pending_intent_tag, newPi)
+        wrapper.onContentUpdated(row)
+        waitForUiOffloadThread()
+        looper.processAllMessages()
+
+        // Listeners for original pending intent need to be cleaned up now.
+        val captor = ArgumentCaptor.forClass(CancelListener::class.java)
+        verify(spy, times(1)).registerCancelListener(captor.capture())
+        verify(spy, times(1)).unregisterCancelListener(captor.value)
+        // Attach listener has to be replaced with a new one.
+        assertThat(action.getTag(com.android.systemui.res.R.id.pending_intent_listener_tag))
+            .isNotEqualTo(attachListener)
+        verify(attachListener).remove()
+    }
+
+    private fun createActionWithPendingIntent(): View {
+        val pi =
+            PendingIntent.getActivity(
+                mContext,
+                System.currentTimeMillis().toInt(),
+                Intent(Intent.ACTION_VIEW),
+                PendingIntent.FLAG_IMMUTABLE
+            )
+        return createActionWithPendingIntent(pi)
+    }
+
+    private fun createActionWithPendingIntent(pi: PendingIntent): View {
+        val view =
+            LayoutInflater.from(mContext)
+                .inflate(R.layout.notification_material_action, null, false)
+        view.setTagInternal(R.id.pending_intent_tag, pi)
+        actions.addView(view)
+        return view
+    }
+
+    private fun getPendingIntent(action: View): PendingIntent {
+        val pendingIntent = action.getTag(R.id.pending_intent_tag) as PendingIntent
+        assertThat(pendingIntent).isNotNull()
+        return pendingIntent
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 8e57dc1..daf8877 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -41,8 +41,8 @@
 import android.app.KeyguardManager;
 import android.content.res.Configuration;
 import android.media.AudioManager;
-import android.os.Handler;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Log;
@@ -71,6 +71,7 @@
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.FakeConfigurationController;
+import com.android.systemui.util.settings.FakeSettings;
 
 import org.junit.After;
 import org.junit.Before;
@@ -135,6 +136,8 @@
     private FakeFeatureFlags mFeatureFlags;
     private int mLongestHideShowAnimationDuration = 250;
 
+    private FakeSettings mSecureSettings;
+
     @Rule
     public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
 
@@ -147,10 +150,6 @@
         mTestableLooper = TestableLooper.get(this);
         allowTestableLooperAsMainThread();
 
-        // Ensure previous tests have not left messages on main looper
-        Handler localHandler = new Handler(mTestableLooper.getLooper());
-        localHandler.removeCallbacksAndMessages(null);
-
         when(mPostureController.getDevicePosture())
                 .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);
 
@@ -167,6 +166,8 @@
 
         mFeatureFlags = new FakeFeatureFlags();
 
+        mSecureSettings = new FakeSettings();
+
         mDialog = new VolumeDialogImpl(
                 getContext(),
                 mVolumeDialogController,
@@ -182,7 +183,8 @@
                 mPostureController,
                 mTestableLooper.getLooper(),
                 mDumpManager,
-                mFeatureFlags);
+                mFeatureFlags,
+                mSecureSettings);
         mDialog.init(0, null);
         State state = createShellState();
         mDialog.onStateChangedH(state);
@@ -247,6 +249,18 @@
     }
 
     @Test
+    public void testSetTimeoutValue_ComputeTimeout() {
+        mSecureSettings.putInt(Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, 7000);
+        Mockito.reset(mAccessibilityMgr);
+        mDialog.init(0, null);
+        mDialog.rescheduleTimeoutH();
+        verify(mAccessibilityMgr).getRecommendedTimeoutMillis(
+                7000,
+                AccessibilityManager.FLAG_CONTENT_CONTROLS);
+    }
+
+
+    @Test
     public void testComputeTimeout_tooltip() {
         Mockito.reset(mAccessibilityMgr);
         mDialog.showCaptionsTooltip();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index 21a5eb7..28557d3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.broadcast
 
 import android.content.BroadcastReceiver
+import android.content.BroadcastReceiver.PendingResult
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
@@ -28,7 +29,6 @@
 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.settings.UserTracker
-import java.lang.IllegalStateException
 import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.Executor
 
@@ -96,8 +96,14 @@
     /**
      * Sends the given [intent] to *only* the receivers that were registered with an [IntentFilter]
      * that matches the intent.
+     *
+     * A non-null [pendingResult] can be used to pass the sending user.
      */
-    fun sendIntentToMatchingReceiversOnly(context: Context, intent: Intent) {
+    fun sendIntentToMatchingReceiversOnly(
+        context: Context,
+        intent: Intent,
+        pendingResult: PendingResult? = null
+    ) {
         receivers.forEach {
             if (
                 it.filter.match(
@@ -107,6 +113,9 @@
                     /* logTag= */ "FakeBroadcastDispatcher",
                 ) > 0
             ) {
+                if (pendingResult != null) {
+                    it.receiver.pendingResult = pendingResult
+                }
                 it.receiver.onReceive(context, intent)
             }
         }
@@ -130,4 +139,19 @@
         val receiver: BroadcastReceiver,
         val filter: IntentFilter,
     )
+
+    companion object {
+        fun fakePendingResultForUser(userId: Int) =
+            PendingResult(
+                /* resultCode = */ 0,
+                /* resultData = */ "",
+                /* resultExtras = */ null,
+                /* type = */ PendingResult.TYPE_REGISTERED,
+                /* ordered = */ false,
+                /* sticky = */ false,
+                /* token = */ null,
+                userId,
+                /* flags = */ 0,
+            )
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt
index bf26e71..cbf4ae5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt
@@ -21,6 +21,6 @@
 
 class FakeQSFactory(private val tileCreator: (String) -> QSTile?) : QSFactory {
     override fun createTile(tileSpec: String): QSTile? {
-        return tileCreator(tileSpec)
+        return tileCreator(tileSpec)?.also { it.tileSpec = tileSpec }
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt
index 9ea079f..57ad2828 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt
@@ -16,15 +16,16 @@
 
 package com.android.systemui.qs.pipeline.data.repository
 
+import com.android.systemui.qs.pipeline.data.model.RestoreData
 import com.android.systemui.qs.pipeline.shared.TileSpec
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 
 class FakeAutoAddRepository : AutoAddRepository {
 
     private val autoAddedTilesPerUser = mutableMapOf<Int, MutableStateFlow<Set<TileSpec>>>()
 
-    override fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> {
+    override suspend fun autoAddedTiles(userId: Int): StateFlow<Set<TileSpec>> {
         return getFlow(userId)
     }
 
@@ -39,4 +40,8 @@
 
     private fun getFlow(userId: Int): MutableStateFlow<Set<TileSpec>> =
         autoAddedTilesPerUser.getOrPut(userId) { MutableStateFlow(emptySet()) }
+
+    override suspend fun reconcileRestore(restoreData: RestoreData) {
+        with(getFlow(restoreData.userId)) { value = value + restoreData.restoredAutoAddedTiles }
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeQSSettingsRestoredRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeQSSettingsRestoredRepository.kt
new file mode 100644
index 0000000..e0c2154
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeQSSettingsRestoredRepository.kt
@@ -0,0 +1,16 @@
+package com.android.systemui.qs.pipeline.data.repository
+
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+
+class FakeQSSettingsRestoredRepository : QSSettingsRestoredRepository {
+    private val _restoreData = MutableSharedFlow<RestoreData>()
+
+    override val restoreData: Flow<RestoreData>
+        get() = _restoreData
+
+    suspend fun onDataRestored(restoreData: RestoreData) {
+        _restoreData.emit(restoreData)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
index aa8dbe1..ae4cf3a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.pipeline.data.repository
 
+import com.android.systemui.qs.pipeline.data.model.RestoreData
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import kotlinx.coroutines.flow.Flow
@@ -26,7 +27,7 @@
 
     private val tilesPerUser = mutableMapOf<Int, MutableStateFlow<List<TileSpec>>>()
 
-    override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
+    override suspend fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
         return getFlow(userId).asStateFlow()
     }
 
@@ -57,4 +58,13 @@
 
     private fun getFlow(userId: Int): MutableStateFlow<List<TileSpec>> =
         tilesPerUser.getOrPut(userId) { MutableStateFlow(emptyList()) }
+
+    override suspend fun reconcileRestore(
+        restoreData: RestoreData,
+        currentAutoAdded: Set<TileSpec>
+    ) {
+        with(getFlow(restoreData.userId)) {
+            value = UserTileSpecRepository.reconcileTiles(value, currentAutoAdded, restoreData)
+        }
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index a3ccb16..9dd0dca 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -113,13 +113,15 @@
     private final boolean mCrossTaskNavigationAllowedByDefault;
     @NonNull
     private final ArraySet<ComponentName> mCrossTaskNavigationExemptions;
+    @Nullable
+    private final ComponentName mPermissionDialogComponent;
     private final Object mGenericWindowPolicyControllerLock = new Object();
     @Nullable private final ActivityBlockedCallback mActivityBlockedCallback;
     private int mDisplayId = Display.INVALID_DISPLAY;
 
     @NonNull
     @GuardedBy("mGenericWindowPolicyControllerLock")
-    final ArraySet<Integer> mRunningUids = new ArraySet<>();
+    private final ArraySet<Integer> mRunningUids = new ArraySet<>();
     @Nullable private final ActivityListener mActivityListener;
     @Nullable private final PipBlockedCallback mPipBlockedCallback;
     @Nullable private final IntentListenerCallback mIntentListenerCallback;
@@ -171,6 +173,7 @@
             @NonNull Set<ComponentName> activityPolicyExemptions,
             boolean crossTaskNavigationAllowedByDefault,
             @NonNull Set<ComponentName> crossTaskNavigationExemptions,
+            @Nullable ComponentName permissionDialogComponent,
             @Nullable ActivityListener activityListener,
             @Nullable PipBlockedCallback pipBlockedCallback,
             @Nullable ActivityBlockedCallback activityBlockedCallback,
@@ -185,6 +188,7 @@
         mActivityPolicyExemptions = activityPolicyExemptions;
         mCrossTaskNavigationAllowedByDefault = crossTaskNavigationAllowedByDefault;
         mCrossTaskNavigationExemptions = new ArraySet<>(crossTaskNavigationExemptions);
+        mPermissionDialogComponent = permissionDialogComponent;
         mActivityBlockedCallback = activityBlockedCallback;
         setInterestedWindowFlags(windowFlags, systemWindowFlags);
         mActivityListener = activityListener;
@@ -309,6 +313,13 @@
             return false;
         }
 
+        // mPermissionDialogComponent being null means we don't want to block permission Dialogs
+        // based on FLAG_STREAM_PERMISSIONS
+        if (mPermissionDialogComponent != null
+                && mPermissionDialogComponent.equals(activityComponent)) {
+            return false;
+        }
+
         return true;
     }
 
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 203a152..a2e4d2c 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -24,6 +24,7 @@
 import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
+import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
@@ -204,6 +205,7 @@
     @GuardedBy("mVirtualDeviceLock")
     @NonNull
     private final Set<ComponentName> mActivityPolicyExemptions;
+    private final ComponentName mPermissionDialogComponent;
 
     private ActivityListener createListenerAdapter() {
         return new ActivityListener() {
@@ -317,6 +319,11 @@
                 mParams.getVirtualSensorCallback(), mParams.getVirtualSensorConfigs());
         mCameraAccessController = cameraAccessController;
         mCameraAccessController.startObservingIfNeeded();
+        if (!Flags.streamPermissions()) {
+            mPermissionDialogComponent = getPermissionDialogComponent();
+        } else {
+            mPermissionDialogComponent = null;
+        }
         try {
             token.linkToDeath(this, 0);
         } catch (RemoteException e) {
@@ -324,8 +331,14 @@
         }
         mVirtualDeviceLog.logCreated(deviceId, mOwnerUid);
 
-        mPublicVirtualDeviceObject = new VirtualDevice(
-                this, getDeviceId(), getPersistentDeviceId(), mParams.getName());
+        if (Flags.vdmPublicApis()) {
+            mPublicVirtualDeviceObject = new VirtualDevice(
+                    this, getDeviceId(), getPersistentDeviceId(), mParams.getName(),
+                    getDisplayName());
+        } else {
+            mPublicVirtualDeviceObject = new VirtualDevice(
+                    this, getDeviceId(), getPersistentDeviceId(), mParams.getName());
+        }
 
         if (Flags.dynamicPolicy()) {
             mActivityPolicyExemptions = new ArraySet<>(
@@ -951,6 +964,7 @@
                 /*crossTaskNavigationExemptions=*/crossTaskNavigationAllowedByDefault
                         ? mParams.getBlockedCrossTaskNavigations()
                         : mParams.getAllowedCrossTaskNavigations(),
+                mPermissionDialogComponent,
                 createListenerAdapter(),
                 this::onEnteringPipBlocked,
                 this::onActivityBlocked,
@@ -963,6 +977,13 @@
         return gwpc;
     }
 
+    private ComponentName getPermissionDialogComponent() {
+        Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
+        PackageManager packageManager = mContext.getPackageManager();
+        intent.setPackage(packageManager.getPermissionControllerPackageName());
+        return intent.resolveActivity(packageManager);
+    }
+
     int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
             @NonNull IVirtualDisplayCallback callback, String packageName) {
         GenericWindowPolicyController gwpc;
diff --git a/services/core/java/com/android/server/connectivity/OWNERS b/services/core/java/com/android/server/connectivity/OWNERS
index 62c5737..c24680e9 100644
--- a/services/core/java/com/android/server/connectivity/OWNERS
+++ b/services/core/java/com/android/server/connectivity/OWNERS
@@ -1,2 +1,2 @@
 set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 13ee47e..40b2f5a 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -43,14 +43,16 @@
 
     BrightnessRangeController(HighBrightnessModeController hbmController,
             Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler,
-            DisplayManagerFlags flags) {
+            DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
         this(hbmController, modeChangeCallback, displayDeviceConfig,
-                new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags);
+                new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags,
+                displayToken, info);
     }
 
     BrightnessRangeController(HighBrightnessModeController hbmController,
             Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig,
-            HdrClamper hdrClamper, DisplayManagerFlags flags) {
+            HdrClamper hdrClamper, DisplayManagerFlags flags, IBinder displayToken,
+            DisplayDeviceInfo info) {
         mHbmController = hbmController;
         mModeChangeCallback = modeChangeCallback;
         mHdrClamper = hdrClamper;
@@ -60,10 +62,7 @@
             mNormalBrightnessModeController.resetNbmData(
                     displayDeviceConfig.getLuxThrottlingData());
         }
-        if (mUseHdrClamper) {
-            mHdrClamper.resetHdrConfig(displayDeviceConfig.getHdrBrightnessData());
-        }
-
+        updateHdrClamper(info, displayToken, displayDeviceConfig);
     }
 
     void dump(PrintWriter pw) {
@@ -101,13 +100,12 @@
                             displayDeviceConfig::getHdrBrightnessFromSdr);
                 }
         );
-        if (mUseHdrClamper) {
-            mHdrClamper.resetHdrConfig(displayDeviceConfig.getHdrBrightnessData());
-        }
+        updateHdrClamper(info, token, displayDeviceConfig);
     }
 
     void stop() {
         mHbmController.stop();
+        mHdrClamper.stop();
     }
 
     void setAutoBrightnessEnabled(int state) {
@@ -151,6 +149,18 @@
         return mHbmController.getTransitionPoint();
     }
 
+    private void updateHdrClamper(DisplayDeviceInfo info, IBinder token,
+            DisplayDeviceConfig displayDeviceConfig) {
+        if (mUseHdrClamper) {
+            DisplayDeviceConfig.HighBrightnessModeData hbmData =
+                    displayDeviceConfig.getHighBrightnessModeData();
+            float minimumHdrPercentOfScreen =
+                    hbmData == null ? -1f : hbmData.minimumHdrPercentOfScreen;
+            mHdrClamper.resetHdrConfig(displayDeviceConfig.getHdrBrightnessData(), info.width,
+                    info.height, minimumHdrPercentOfScreen, token);
+        }
+    }
+
     private void applyChanges(BooleanSupplier nbmChangesFunc, Runnable hbmChangesFunc) {
         if (mUseNbmController) {
             boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean();
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 8b73493..ce98559 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -687,7 +687,9 @@
         HighBrightnessModeController hbmController = createHbmControllerLocked(modeChangeCallback);
 
         mBrightnessRangeController = new BrightnessRangeController(hbmController,
-                modeChangeCallback, mDisplayDeviceConfig, mHandler, flags);
+                modeChangeCallback, mDisplayDeviceConfig, mHandler, flags,
+                mDisplayDevice.getDisplayTokenLocked(),
+                mDisplayDevice.getDisplayDeviceInfoLocked());
 
         mBrightnessThrottler = createBrightnessThrottlerLocked();
 
@@ -1958,6 +1960,7 @@
                 // We want to scale HDR brightness level with the SDR level, we also need to restore
                 // SDR brightness immediately when entering dim or low power mode.
                 animateValue = mBrightnessRangeController.getHdrBrightnessValue();
+                mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR);
             }
 
             final float currentBrightness = mPowerState.getScreenBrightness();
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 80644da..1652871 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -552,7 +552,9 @@
         mBrightnessThrottler = createBrightnessThrottlerLocked();
 
         mBrightnessRangeController = mInjector.getBrightnessRangeController(hbmController,
-                modeChangeCallback, mDisplayDeviceConfig, mHandler, flags);
+                modeChangeCallback, mDisplayDeviceConfig, mHandler, flags,
+                mDisplayDevice.getDisplayTokenLocked(),
+                mDisplayDevice.getDisplayDeviceInfoLocked());
 
         mDisplayBrightnessController =
                 new DisplayBrightnessController(context, null,
@@ -1564,6 +1566,7 @@
                 // SDR brightness immediately when entering dim or low power mode.
                 animateValue = mBrightnessRangeController.getHdrBrightnessValue();
                 customTransitionRate = mBrightnessRangeController.getHdrTransitionRate();
+                mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR);
             }
 
             final float currentBrightness = mPowerState.getScreenBrightness();
@@ -1895,15 +1898,12 @@
 
     private HighBrightnessModeController createHbmControllerLocked(
             HighBrightnessModeMetadata hbmMetadata, Runnable modeChangeCallback) {
-        final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
-        final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
-        final IBinder displayToken =
-                mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked();
-        final String displayUniqueId =
-                mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+        final DisplayDeviceConfig ddConfig = mDisplayDevice.getDisplayDeviceConfig();
+        final IBinder displayToken = mDisplayDevice.getDisplayTokenLocked();
+        final String displayUniqueId = mDisplayDevice.getUniqueId();
         final DisplayDeviceConfig.HighBrightnessModeData hbmData =
                 ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
-        final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+        final DisplayDeviceInfo info = mDisplayDevice.getDisplayDeviceInfoLocked();
         return mInjector.getHighBrightnessModeController(mHandler, info.width, info.height,
                 displayToken, displayUniqueId, PowerManager.BRIGHTNESS_MIN,
                 PowerManager.BRIGHTNESS_MAX, hbmData, (sdrBrightness, maxDesiredHdrSdrRatio) ->
@@ -3049,9 +3049,9 @@
         BrightnessRangeController getBrightnessRangeController(
                 HighBrightnessModeController hbmController, Runnable modeChangeCallback,
                 DisplayDeviceConfig displayDeviceConfig, Handler handler,
-                DisplayManagerFlags flags) {
+                DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
             return new BrightnessRangeController(hbmController,
-                    modeChangeCallback, displayDeviceConfig, handler, flags);
+                    modeChangeCallback, displayDeviceConfig, handler, flags, displayToken, info);
         }
 
         DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index a514136..e46edd9 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -17,9 +17,13 @@
 package com.android.server.display.brightness.clamper;
 
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.PowerManager;
+import android.view.SurfaceControlHdrLayerInfoListener;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.config.HdrBrightnessData;
 
 import java.io.PrintWriter;
@@ -33,11 +37,18 @@
 
     private final Runnable mDebouncer;
 
+    private final HdrLayerInfoListener mHdrListener;
+
     @Nullable
     private HdrBrightnessData mHdrBrightnessData = null;
 
+    @Nullable
+    private IBinder mRegisteredDisplayToken = null;
+
     private float mAmbientLux = Float.MAX_VALUE;
 
+    private boolean mHdrVisible = false;
+
     private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
     private float mDesiredMaxBrightness = PowerManager.BRIGHTNESS_MAX;
 
@@ -47,6 +58,12 @@
 
     public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener,
             Handler handler) {
+        this(clamperChangeListener, handler, new Injector());
+    }
+
+    @VisibleForTesting
+    public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+            Handler handler, Injector injector) {
         mClamperChangeListener = clamperChangeListener;
         mHandler = handler;
         mDebouncer = () -> {
@@ -54,6 +71,10 @@
             mMaxBrightness = mDesiredMaxBrightness;
             mClamperChangeListener.onChanged();
         };
+        mHdrListener = injector.getHdrListener((visible) -> {
+            mHdrVisible = visible;
+            recalculateBrightnessCap(mHdrBrightnessData, mAmbientLux, mHdrVisible);
+        }, handler);
     }
 
     // Called in same looper: mHandler.getLooper()
@@ -72,16 +93,37 @@
      */
     public void onAmbientLuxChange(float ambientLux) {
         mAmbientLux = ambientLux;
-        recalculateBrightnessCap(mHdrBrightnessData, ambientLux);
+        recalculateBrightnessCap(mHdrBrightnessData, ambientLux, mHdrVisible);
     }
 
     /**
      * Updates brightness cap config.
      * Called in same looper: mHandler.getLooper()
      */
-    public void resetHdrConfig(HdrBrightnessData data) {
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public void resetHdrConfig(HdrBrightnessData data, int width, int height,
+            float minimumHdrPercentOfScreen, IBinder displayToken) {
         mHdrBrightnessData = data;
-        recalculateBrightnessCap(data, mAmbientLux);
+        mHdrListener.mHdrMinPixels = (float) (width * height) * minimumHdrPercentOfScreen;
+        if (displayToken != mRegisteredDisplayToken) { // token changed, resubscribe
+            if (mRegisteredDisplayToken != null) { // previous token not null, unsubscribe
+                mHdrListener.unregister(mRegisteredDisplayToken);
+                mHdrVisible = false;
+            }
+            if (displayToken != null) { // new token not null, subscribe
+                mHdrListener.register(displayToken);
+            }
+            mRegisteredDisplayToken = displayToken;
+        }
+        recalculateBrightnessCap(data, mAmbientLux, mHdrVisible);
+    }
+
+    /** Clean up all resources */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public void stop() {
+        if (mRegisteredDisplayToken != null) {
+            mHdrListener.unregister(mRegisteredDisplayToken);
+        }
     }
 
     /**
@@ -98,13 +140,28 @@
         pw.println("  mAmbientLux=" + mAmbientLux);
     }
 
-    private void recalculateBrightnessCap(HdrBrightnessData data, float ambientLux) {
-        if (data == null) {
-            mHandler.removeCallbacks(mDebouncer);
+    private void reset() {
+        if (mMaxBrightness == PowerManager.BRIGHTNESS_MAX
+                && mDesiredMaxBrightness == PowerManager.BRIGHTNESS_MAX && mTransitionRate == -1f
+                && mDesiredTransitionRate == -1f) { // already done reset, do nothing
             return;
         }
-        float expectedMaxBrightness = findBrightnessLimit(data, ambientLux);
+        mHandler.removeCallbacks(mDebouncer);
+        mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+        mDesiredMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+        mDesiredTransitionRate = -1f;
+        mTransitionRate = 1f;
+        mClamperChangeListener.onChanged();
+    }
 
+    private void recalculateBrightnessCap(HdrBrightnessData data, float ambientLux,
+            boolean hdrVisible) {
+        if (data == null || !hdrVisible) {
+            reset();
+            return;
+        }
+
+        float expectedMaxBrightness = findBrightnessLimit(data, ambientLux);
         if (mMaxBrightness == expectedMaxBrightness) {
             mDesiredMaxBrightness = mMaxBrightness;
             mDesiredTransitionRate = -1f;
@@ -127,6 +184,8 @@
             mHandler.removeCallbacks(mDebouncer);
             mHandler.postDelayed(mDebouncer, debounceTime);
         }
+        // do nothing if expectedMaxBrightness == mDesiredMaxBrightness
+        // && expectedMaxBrightness != mMaxBrightness
     }
 
     private float findBrightnessLimit(HdrBrightnessData data, float ambientLux) {
@@ -143,4 +202,36 @@
         }
         return foundMaxBrightness;
     }
+
+    @FunctionalInterface
+    interface HdrListener {
+        void onHdrVisible(boolean visible);
+    }
+
+    static class HdrLayerInfoListener extends SurfaceControlHdrLayerInfoListener {
+        private final HdrListener mHdrListener;
+
+        private final Handler mHandler;
+
+        private float mHdrMinPixels = Float.MAX_VALUE;
+
+        HdrLayerInfoListener(HdrListener hdrListener, Handler handler) {
+            mHdrListener = hdrListener;
+            mHandler = handler;
+        }
+
+        @Override
+        public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW,
+                int maxH, int flags, float maxDesiredHdrSdrRatio) {
+            mHandler.post(() ->
+                    mHdrListener.onHdrVisible(
+                            numberOfHdrLayers > 0 && (float) (maxW * maxH) >= mHdrMinPixels));
+        }
+    }
+
+    static class Injector {
+        HdrLayerInfoListener getHdrListener(HdrListener hdrListener, Handler handler) {
+            return new HdrLayerInfoListener(hdrListener, handler);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/net/OWNERS b/services/core/java/com/android/server/net/OWNERS
index 9c96d46f..d0e95dd 100644
--- a/services/core/java/com/android/server/net/OWNERS
+++ b/services/core/java/com/android/server/net/OWNERS
@@ -1,5 +1,5 @@
 set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
 
 jsharkey@android.com
 sudheersai@google.com
diff --git a/services/core/java/com/android/server/pm/PackageManagerException.java b/services/core/java/com/android/server/pm/PackageManagerException.java
index 881cbe8..d69737a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerException.java
+++ b/services/core/java/com/android/server/pm/PackageManagerException.java
@@ -102,7 +102,8 @@
             INTERNAL_ERROR_STATIC_SHARED_LIB_PROTECTED_BROADCAST,
             INTERNAL_ERROR_STATIC_SHARED_LIB_OVERLAY_TARGETS,
             INTERNAL_ERROR_APEX_NOT_DIRECTORY,
-            INTERNAL_ERROR_APEX_MORE_THAN_ONE_FILE
+            INTERNAL_ERROR_APEX_MORE_THAN_ONE_FILE,
+            INTERNAL_ERROR_MISSING_USER
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface InternalErrorCode {}
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 3d61f3e..b055a3f 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -402,7 +402,7 @@
                 mBroadcastHelper.sendPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
             }
         } else if (!deletedPs.isSystem() && outInfo != null && !outInfo.mIsUpdate
-                && outInfo.mRemovedUsers != null) {
+                && outInfo.mRemovedUsers != null && !outInfo.mIsExternal) {
             // For non-system uninstalls with DELETE_KEEP_DATA, set the installed state to false
             // for affected users. This does not apply to app updates where the old apk is replaced
             // but the old data remains.
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 04d1da6..390c45b 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -145,6 +145,19 @@
           "include-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest"
         }
       ]
+    },
+    {
+      "file_patterns": [
+        "(/|^)InstallPackageHelper\\.java",
+        "services/core/java/com/android/server/pm/parsing/.*",
+        "services/core/java/com/android/server/pm/pkg/parsing/.*"
+      ],
+      "name": "SdkSandboxManagerServiceUnitTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
     }
   ],
   "imports": [
diff --git a/services/core/java/com/android/server/security/rkp/OWNERS b/services/core/java/com/android/server/security/rkp/OWNERS
index 348f940..ea6dc72 100644
--- a/services/core/java/com/android/server/security/rkp/OWNERS
+++ b/services/core/java/com/android/server/security/rkp/OWNERS
@@ -1 +1 @@
-file:platform/frameworks/base:master:/core/java/android/security/rkp/OWNERS
+file:platform/frameworks/base:main:/core/java/android/security/rkp/OWNERS
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 40e9c13..88eaafa 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -955,6 +955,17 @@
         }
     }
 
+    public void setTiles(String tiles) {
+        enforceStatusBarOrShell();
+
+        if (mBar != null) {
+            try {
+                mBar.setQsTiles(tiles.split(","));
+            } catch (RemoteException ex) {
+            }
+        }
+    }
+
     public void clickTile(ComponentName component) {
         enforceStatusBarOrShell();
 
diff --git a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
index 11a4976d..d6bf02f 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
@@ -61,6 +61,8 @@
                     return runAddTile();
                 case "remove-tile":
                     return runRemoveTile();
+                case "set-tiles":
+                    return runSetTiles();
                 case "click-tile":
                     return runClickTile();
                 case "check-support":
@@ -105,6 +107,11 @@
         return 0;
     }
 
+    private int runSetTiles() throws RemoteException {
+        mInterface.setTiles(getNextArgRequired());
+        return 0;
+    }
+
     private int runClickTile() throws RemoteException {
         mInterface.clickTile(ComponentName.unflattenFromString(getNextArgRequired()));
         return 0;
@@ -242,6 +249,9 @@
         pw.println("  remove-tile COMPONENT");
         pw.println("    Remove a TileService of the specified component");
         pw.println("");
+        pw.println("  set-tiles LIST-OF-TILES");
+        pw.println("    Sets the list of tiles as the current Quick Settings tiles");
+        pw.println("");
         pw.println("  click-tile COMPONENT");
         pw.println("    Click on a TileService of the specified component");
         pw.println("");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a0c7870..ed10346 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -580,7 +580,7 @@
     IBinder mRequestedLaunchingTaskFragmentToken;
 
     // Tracking splash screen status from previous activity
-    boolean mAllowIconSplashScreen = true;
+    boolean mSplashScreenStyleSolidColor = false;
 
     boolean mPauseSchedulePendingForPip = false;
 
@@ -2408,7 +2408,8 @@
     @VisibleForTesting
     boolean addStartingWindow(String pkg, int resolvedTheme, ActivityRecord from, boolean newTask,
             boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot,
-            boolean activityCreated, boolean allowIcon, boolean activityAllDrawn) {
+            boolean activityCreated, boolean isSimple,
+            boolean activityAllDrawn) {
         // If the display is frozen, we won't do anything until the actual window is
         // displayed so there is no reason to put in the starting window.
         if (!okToDisplay()) {
@@ -2443,8 +2444,8 @@
 
         final int typeParameter = StartingSurfaceController
                 .makeStartingWindowTypeParameter(newTask, taskSwitch, processRunning,
-                        allowTaskSnapshot, activityCreated, allowIcon, useLegacy,
-                        activityAllDrawn, type, packageName, mUserId);
+                        allowTaskSnapshot, activityCreated, isSimple, useLegacy, activityAllDrawn,
+                        type, packageName, mUserId);
 
         if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
             if (isActivityTypeHome()) {
@@ -5365,7 +5366,7 @@
                 // Finish should only ever commit visibility=false, so we can check full containment
                 // rather than just direct membership.
                 inFinishingTransition = mTransitionController.inFinishingTransition(this);
-                if (!inFinishingTransition && !mDisplayContent.isSleeping()) {
+                if (!inFinishingTransition && (visible || !mDisplayContent.isSleeping())) {
                     Slog.e(TAG, "setVisibility=" + visible
                             + " while transition is not collecting or finishing "
                             + this + " caller=" + Debug.getCallers(8));
@@ -6746,7 +6747,7 @@
     void onFirstWindowDrawn(WindowState win) {
         firstWindowDrawn = true;
         // stop tracking
-        mAllowIconSplashScreen = false;
+        mSplashScreenStyleSolidColor = true;
 
         if (mStartingWindow != null) {
             ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Finish starting %s"
@@ -6795,7 +6796,7 @@
     void onStartingWindowDrawn() {
         boolean wasTaskVisible = false;
         if (task != null) {
-            mAllowIconSplashScreen = false;
+            mSplashScreenStyleSolidColor = true;
             wasTaskVisible = !setTaskHasBeenVisible();
         }
 
@@ -7320,32 +7321,19 @@
     }
 
     /**
-     * Checks whether an icon splash screen can be used in the starting window based on the
-     * preference in the {@code options} and this activity's theme, giving higher priority to the
-     * {@code options}'s preference.
-     *
-     * When no preference is specified, a default behaviour is defined:
-     *  - if the activity is started from the home or shell app, an icon can be used
-     *  - if the activity is started from SystemUI, an icon should not be used
-     *  - if there is a launching activity, use its preference
-     *  - if none of the above is met, only use an icon when the activity is started for the first
-     *    time from a System app
-     *
-     * The returned value is sent to WmShell, which will make the final decision on what splash
-     * screen type will be used.
-     *
-     * @return true if an icon can be used in the splash screen
-     *         false when an icon should not be used in the splash screen
+     * @return true if a solid color splash screen must be used
+     *         false when an icon splash screen can be used, but the final decision for whether to
+     *               use an icon or solid color splash screen will be made by WmShell.
      */
-    private boolean canUseIconSplashScreen(ActivityRecord sourceRecord,
+    private boolean shouldUseSolidColorSplashScreen(ActivityRecord sourceRecord,
             boolean startActivity, ActivityOptions options, int resolvedTheme) {
         if (sourceRecord == null && !startActivity) {
-            // Shouldn't use an icon if this activity is not top activity. This could happen when
-            // adding a splash screen window to the warm start activity which is re-create because
-            // top is finishing.
+            // Use simple style if this activity is not top activity. This could happen when adding
+            // a splash screen window to the warm start activity which is re-create because top is
+            // finishing.
             final ActivityRecord above = task.getActivityAbove(this);
             if (above != null) {
-                return false;
+                return true;
             }
         }
 
@@ -7353,33 +7341,32 @@
         final int optionsStyle = options != null ? options.getSplashScreenStyle() :
                 SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
         if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR) {
-            return false;
+            return true;
         } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON
                     || isIconStylePreferred(resolvedTheme)) {
-            return true;
+            return false;
         }
 
         // Choose the default behavior when neither the ActivityRecord nor the activity theme have
         // specified a splash screen style.
 
         if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME || launchedFromUid == Process.SHELL_UID) {
-            return true;
-        } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) {
             return false;
+        } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) {
+            return true;
         } else {
-            // Need to check sourceRecord in case this activity is launched from a service or a
-            // trampoline activity.
+            // Need to check sourceRecord in case this activity is launched from a service.
             if (sourceRecord == null) {
                 sourceRecord = searchCandidateLaunchingActivity();
             }
 
             if (sourceRecord != null) {
-                return sourceRecord.mAllowIconSplashScreen;
+                return sourceRecord.mSplashScreenStyleSolidColor;
             }
 
             // Use an icon if the activity was launched from System for the first start.
-            // Otherwise, can't use an icon splash screen.
-            return mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEM && startActivity;
+            // Otherwise, must use solid color splash screen.
+            return mLaunchSourceType != LAUNCH_SOURCE_TYPE_SYSTEM || !startActivity;
         }
     }
 
@@ -7443,7 +7430,7 @@
         final int resolvedTheme = evaluateStartingWindowTheme(prev, packageName, theme,
                 splashScreenTheme);
 
-        mAllowIconSplashScreen = canUseIconSplashScreen(sourceRecord, startActivity,
+        mSplashScreenStyleSolidColor = shouldUseSolidColorSplashScreen(sourceRecord, startActivity,
                 startOptions, resolvedTheme);
 
         final boolean activityCreated =
@@ -7455,7 +7442,7 @@
 
         final boolean scheduled = addStartingWindow(packageName, resolvedTheme,
                 prev, newTask || newSingleActivity, taskSwitch, processRunning,
-                allowTaskSnapshot(), activityCreated, mAllowIconSplashScreen, allDrawn);
+                allowTaskSnapshot(), activityCreated, mSplashScreenStyleSolidColor, allDrawn);
         if (DEBUG_STARTING_WINDOW_VERBOSE && scheduled) {
             Slog.d(TAG, "Scheduled starting window for " + this);
         }
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 184de58..442269a 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -64,6 +64,7 @@
 import android.os.Environment;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -506,6 +507,16 @@
 
         Slog.i(TAG, "Loading recents for user " + userId + " into memory.");
         List<Task> tasks = mTaskPersister.restoreTasksForUserLocked(userId, preaddedTasks);
+
+        // Tasks are ordered from most recent to least recent. Update the last active time to be
+        // in sync with task recency when device reboots, so the most recent task has the
+        // highest last active time
+        long currentElapsedTime = SystemClock.elapsedRealtime();
+        for (int i = 0; i < tasks.size(); i++) {
+            Task task = tasks.get(i);
+            task.lastActiveTime = currentElapsedTime - i;
+        }
+
         mTasks.addAll(tasks);
         cleanupLocked(userId);
         mUsersWithRecentsLoaded.put(userId, true);
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index a0517be..a55c232 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -19,12 +19,12 @@
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_DRAWN;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN;
-import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_ICON;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_LEGACY_SPLASH_SCREEN;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN;
 
 import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_TYPE_SNAPSHOT;
 import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
@@ -102,7 +102,7 @@
 
     static int makeStartingWindowTypeParameter(boolean newTask, boolean taskSwitch,
             boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated,
-            boolean allowIcon, boolean useLegacy, boolean activityDrawn, int startingWindowType,
+            boolean isSolidColor, boolean useLegacy, boolean activityDrawn, int startingWindowType,
             String packageName, int userId) {
         int parameter = 0;
         if (newTask) {
@@ -120,8 +120,8 @@
         if (activityCreated || startingWindowType == STARTING_WINDOW_TYPE_SNAPSHOT) {
             parameter |= TYPE_PARAMETER_ACTIVITY_CREATED;
         }
-        if (allowIcon) {
-            parameter |= TYPE_PARAMETER_ALLOW_ICON;
+        if (isSolidColor) {
+            parameter |= TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN;
         }
         if (useLegacy) {
             parameter |= TYPE_PARAMETER_LEGACY_SPLASH_SCREEN;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index d0ead14..25e8475 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -21,6 +21,7 @@
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_UPDATE_RESULT_KEY;
 import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_CONFLICTING_ADMIN_POLICY;
 import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_HARDWARE_LIMITATION;
+import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_STORAGE_LIMIT_REACHED;
 import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED;
 import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_SET;
 import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT;
@@ -51,6 +52,7 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -63,6 +65,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.devicepolicy.flags.FlagUtils;
 import com.android.server.utils.Slogf;
 
 import libcore.io.IoUtils;
@@ -117,6 +120,10 @@
      * Map containing the current set of admins in each user with active policies.
      */
     private final SparseArray<Set<EnforcingAdmin>> mEnforcingAdmins;
+    private final SparseArray<HashMap<EnforcingAdmin, Integer>> mAdminPolicySize;
+
+    //TODO(b/295504706) : Speak to security team to decide what to set Policy_Size_Limit
+    private static final int POLICY_SIZE_LIMIT = 99999;
 
     private final DeviceAdminServiceController mDeviceAdminServiceController;
 
@@ -131,6 +138,7 @@
         mLocalPolicies = new SparseArray<>();
         mGlobalPolicies = new HashMap<>();
         mEnforcingAdmins = new SparseArray<>();
+        mAdminPolicySize = new SparseArray<>();
     }
 
     /**
@@ -139,7 +147,6 @@
      *
      * <p>If {@code skipEnforcePolicy} is true, it sets the policies in the internal data structure
      * but doesn't call the enforcing logic.
-     *
      */
     <V> void setLocalPolicy(
             @NonNull PolicyDefinition<V> policyDefinition,
@@ -152,6 +159,12 @@
 
         synchronized (mLock) {
             PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
+            if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+                if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value,
+                        policyDefinition, userId)) {
+                    return;
+                }
+            }
 
             if (policyDefinition.isNonCoexistablePolicy()) {
                 setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState,
@@ -236,6 +249,7 @@
     }
 
     // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
+
     /**
      * Set the policy for the provided {@code policyDefinition}
      * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}.
@@ -250,6 +264,7 @@
     }
 
     // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
+
     /**
      * Removes any previously set policy for the provided {@code policyDefinition}
      * (see {@link PolicyDefinition}) and {@code enforcingAdmin}.
@@ -267,6 +282,10 @@
             }
             PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
 
+            if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+                decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin);
+            }
+
             if (policyDefinition.isNonCoexistablePolicy()) {
                 setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState,
                         enforcingAdmin, /* value= */ null, userId, /* skipEnforcePolicy= */ false);
@@ -392,6 +411,7 @@
     }
 
     // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
+
     /**
      * Set the policy for the provided {@code policyDefinition}
      * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}.
@@ -407,6 +427,13 @@
         Objects.requireNonNull(value);
 
         synchronized (mLock) {
+            PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
+            if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+                if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value,
+                        policyDefinition, UserHandle.USER_ALL)) {
+                    return;
+                }
+            }
             // TODO(b/270999567): Move error handling for DISALLOW_CELLULAR_2G into the code
             //  that honors the restriction once there's an API available
             if (checkFor2gFailure(policyDefinition, enforcingAdmin)) {
@@ -416,8 +443,6 @@
                 return;
             }
 
-            PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
-
             boolean policyChanged = globalPolicyState.addPolicy(enforcingAdmin, value);
             boolean policyAppliedOnAllUsers = applyGlobalPolicyOnUsersWithLocalPoliciesLocked(
                     policyDefinition, enforcingAdmin, value, skipEnforcePolicy);
@@ -434,7 +459,7 @@
                 // TODO(b/285532044): remove hack and handle properly
                 if (!policyAppliedGlobally
                         && policyDefinition.getPolicyKey().getIdentifier().equals(
-                                USER_CONTROL_DISABLED_PACKAGES_POLICY)) {
+                        USER_CONTROL_DISABLED_PACKAGES_POLICY)) {
                     PolicyValue<Set<String>> parsedValue = (PolicyValue<Set<String>>) value;
                     PolicyValue<Set<String>> parsedResolvedValue =
                             (PolicyValue<Set<String>>) globalPolicyState.getCurrentResolvedPolicy();
@@ -459,6 +484,7 @@
     }
 
     // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
+
     /**
      * Removes any previously set policy for the provided {@code policyDefinition}
      * (see {@link PolicyDefinition}) and {@code enforcingAdmin}.
@@ -472,6 +498,11 @@
 
         synchronized (mLock) {
             PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
+
+            if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+                decreasePolicySizeForAdmin(policyState, enforcingAdmin);
+            }
+
             boolean policyChanged = policyState.removePolicy(enforcingAdmin);
 
             if (policyChanged) {
@@ -687,7 +718,6 @@
      * <p>Note that this will always return at most one item for policies that do not require
      * additional params (e.g. {@link PolicyDefinition#LOCK_TASK} vs
      * {@link PolicyDefinition#PERMISSION_GRANT(String, String)}).
-     *
      */
     @NonNull
     <V> Set<PolicyKey> getLocalPolicyKeysSetByAdmin(
@@ -723,7 +753,6 @@
      * <p>Note that this will always return at most one item for policies that do not require
      * additional params (e.g. {@link PolicyDefinition#LOCK_TASK} vs
      * {@link PolicyDefinition#PERMISSION_GRANT(String, String)}).
-     *
      */
     @NonNull
     <V> Set<PolicyKey> getLocalPolicyKeysSetByAllAdmins(
@@ -964,7 +993,7 @@
             EnforcingAdmin callingAdmin,
             PolicyDefinition<V> policyDefinition,
             int userId) {
-        for (EnforcingAdmin admin: policyState.getPoliciesSetByAdmins().keySet()) {
+        for (EnforcingAdmin admin : policyState.getPoliciesSetByAdmins().keySet()) {
             // We're sending a separate broadcast for the calling admin with the result.
             if (admin.equals(callingAdmin)) {
                 continue;
@@ -1152,7 +1181,7 @@
                     try {
                         if (packageManager.getPackageInfo(packageName, 0, userId) == null
                                 || packageManager.getActivityInfo(
-                                        policies.get(admin).getValue(), 0, userId) == null) {
+                                policies.get(admin).getValue(), 0, userId) == null) {
                             Slogf.e(TAG, String.format(
                                     "Persistent preferred activity in package %s not found for "
                                             + "user %d, removing policy for admin",
@@ -1450,6 +1479,97 @@
         return false;
     }
 
+    /**
+     * Calculate the size of a policy in bytes
+     */
+
+    private static <V> int sizeOf(PolicyValue<V> value) {
+        try {
+            Parcel parcel = Parcel.obtain();
+            parcel.writeParcelable(value, /* flags= */ 0);
+
+            parcel.setDataPosition(0);
+
+            byte[] bytes;
+
+            bytes = parcel.marshall();
+            return bytes.length;
+        } catch (Exception e) {
+            Log.e(TAG, "Error calculating size of policy: " + e);
+            return 0;
+        }
+    }
+
+    /**
+     * Checks if the policy already exists and removes the current size to prevent recording the
+     * same policy twice.
+     *
+     * Checks if the new sum of the size of all policies is less than the maximum sum of policies
+     * size per admin and returns true.
+     *
+     * If the policy size limit is reached then send policy result to admin and return false.
+     */
+
+    private <V> boolean handleAdminPolicySizeLimit(PolicyState<V> policyState, EnforcingAdmin admin,
+            PolicyValue<V> value, PolicyDefinition policyDefinition, int userId) {
+        int currentSize = 0;
+        if (mAdminPolicySize.contains(admin.getUserId())
+                && mAdminPolicySize.get(
+                admin.getUserId()).containsKey(admin)) {
+            currentSize = mAdminPolicySize.get(admin.getUserId()).get(admin);
+        }
+        if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
+            currentSize -= sizeOf(policyState.getPoliciesSetByAdmins().get(admin));
+        }
+        int policySize = sizeOf(value);
+        if (currentSize + policySize < POLICY_SIZE_LIMIT) {
+            increasePolicySizeForAdmin(admin, policySize);
+            return true;
+        } else {
+            sendPolicyResultToAdmin(
+                    admin,
+                    policyDefinition,
+                    RESULT_FAILURE_STORAGE_LIMIT_REACHED,
+                    userId);
+            return false;
+        }
+    }
+
+    /**
+     * Increase the int in mAdminPolicySize representing the size of the sum of all
+     * active policies for that admin.
+     */
+
+    private <V> void increasePolicySizeForAdmin(EnforcingAdmin admin, int policySize) {
+        if (!mAdminPolicySize.contains(admin.getUserId())) {
+            mAdminPolicySize.put(admin.getUserId(), new HashMap<>());
+        }
+        if (!mAdminPolicySize.get(admin.getUserId()).containsKey(admin)) {
+            mAdminPolicySize.get(admin.getUserId()).put(admin, /* size= */ 0);
+        }
+        mAdminPolicySize.get(admin.getUserId()).put(admin,
+                mAdminPolicySize.get(admin.getUserId()).get(admin) + policySize);
+    }
+
+    /**
+     * Decrease the int in mAdminPolicySize representing the size of the sum of all
+     * active policies for that admin.
+     */
+
+    private <V> void decreasePolicySizeForAdmin(PolicyState<V> policyState, EnforcingAdmin admin) {
+        if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
+            mAdminPolicySize.get(admin.getUserId()).put(admin,
+                    mAdminPolicySize.get(admin.getUserId()).get(admin) - sizeOf(
+                            policyState.getPoliciesSetByAdmins().get(admin)));
+        }
+        if (mAdminPolicySize.get(admin.getUserId()).get(admin) <= 0) {
+            mAdminPolicySize.get(admin.getUserId()).remove(admin);
+        }
+        if (mAdminPolicySize.get(admin.getUserId()).isEmpty()) {
+            mAdminPolicySize.remove(admin.getUserId());
+        }
+    }
+
     @NonNull
     private Set<EnforcingAdmin> getEnforcingAdminsOnUser(int userId) {
         synchronized (mLock) {
@@ -1508,11 +1628,13 @@
         clear();
         write();
     }
+
     private void clear() {
         synchronized (mLock) {
             mGlobalPolicies.clear();
             mLocalPolicies.clear();
             mEnforcingAdmins.clear();
+            mAdminPolicySize.clear();
         }
     }
 
@@ -1553,7 +1675,11 @@
         private static final String TAG_POLICY_STATE_ENTRY = "policy-state-entry";
         private static final String TAG_POLICY_KEY_ENTRY = "policy-key-entry";
         private static final String TAG_ENFORCING_ADMINS_ENTRY = "enforcing-admins-entry";
+        private static final String TAG_ENFORCING_ADMIN_AND_SIZE = "enforcing-admin-and-size";
+        private static final String TAG_ENFORCING_ADMIN = "enforcing-admin";
+        private static final String TAG_POLICY_SUM_SIZE = "policy-sum-size";
         private static final String ATTR_USER_ID = "user-id";
+        private static final String ATTR_POLICY_SUM_SIZE = "size";
 
         private final File mFile;
 
@@ -1595,6 +1721,7 @@
             writeLocalPoliciesInner(serializer);
             writeGlobalPoliciesInner(serializer);
             writeEnforcingAdminsInner(serializer);
+            writeEnforcingAdminSizeInner(serializer);
         }
 
         private void writeLocalPoliciesInner(TypedXmlSerializer serializer) throws IOException {
@@ -1652,6 +1779,30 @@
             }
         }
 
+        private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer)
+                throws IOException {
+            if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+                if (mAdminPolicySize != null) {
+                    for (int i = 0; i < mAdminPolicySize.size(); i++) {
+                        int userId = mAdminPolicySize.keyAt(i);
+                        for (EnforcingAdmin admin : mAdminPolicySize.get(
+                                userId).keySet()) {
+                            serializer.startTag(/* namespace= */ null,
+                                    TAG_ENFORCING_ADMIN_AND_SIZE);
+                            serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN);
+                            admin.saveToXml(serializer);
+                            serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN);
+                            serializer.startTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE);
+                            serializer.attributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE,
+                                    mAdminPolicySize.get(userId).get(admin));
+                            serializer.endTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE);
+                            serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_AND_SIZE);
+                        }
+                    }
+                }
+            }
+        }
+
         void readFromFileLocked() {
             if (!mFile.exists()) {
                 Log.d(TAG, "" + mFile + " doesn't exist");
@@ -1689,6 +1840,9 @@
                     case TAG_ENFORCING_ADMINS_ENTRY:
                         readEnforcingAdminsInner(parser);
                         break;
+                    case TAG_ENFORCING_ADMIN_AND_SIZE:
+                        readEnforcingAdminAndSizeInner(parser);
+                        break;
                     default:
                         Slogf.wtf(TAG, "Unknown tag " + tag);
                 }
@@ -1767,5 +1921,37 @@
             }
             mEnforcingAdmins.get(admin.getUserId()).add(admin);
         }
+
+        private void readEnforcingAdminAndSizeInner(TypedXmlPullParser parser)
+                throws XmlPullParserException, IOException {
+            int outerDepth = parser.getDepth();
+            EnforcingAdmin admin = null;
+            int size = 0;
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                String tag = parser.getName();
+                switch (tag) {
+                    case TAG_ENFORCING_ADMIN:
+                        admin = EnforcingAdmin.readFromXml(parser);
+                        break;
+                    case TAG_POLICY_SUM_SIZE:
+                        size = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE);
+                        break;
+                    default:
+                        Slogf.wtf(TAG, "Unknown tag " + tag);
+                }
+            }
+            if (admin == null) {
+                Slogf.wtf(TAG, "Error parsing enforcingAdmins, EnforcingAdmin is null.");
+                return;
+            }
+            if (size <= 0) {
+                Slogf.wtf(TAG, "Error parsing policy size, size is " + size);
+                return;
+            }
+            if (!mAdminPolicySize.contains(admin.getUserId())) {
+                mAdminPolicySize.put(admin.getUserId(), new HashMap<>());
+            }
+            mAdminPolicySize.get(admin.getUserId()).put(admin, size);
+        }
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f604932..6aa135a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -241,7 +241,6 @@
 import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI;
 import static android.provider.Telephony.Carriers.INVALID_APN_ID;
 import static android.security.keystore.AttestationUtils.USE_INDIVIDUAL_ATTESTATION;
-
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
@@ -23455,7 +23454,6 @@
     public DevicePolicyState getDevicePolicyState() {
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
-
         return mInjector.binderWithCleanCallingIdentity(mDevicePolicyEngine::getDevicePolicyState);
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
index 9fe3749..7e17ef11 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.server.devicepolicy.flags;
 
+import static com.android.server.devicepolicy.flags.Flags.devicePolicySizeTrackingEnabled;
 import static com.android.server.devicepolicy.flags.Flags.policyEngineMigrationV2Enabled;
 
 import android.os.Binder;
@@ -28,4 +29,10 @@
             return policyEngineMigrationV2Enabled();
         });
     }
+
+    public static boolean isDevicePolicySizeTrackingEnabled() {
+        return Binder.withCleanCallingIdentity(() -> {
+            return devicePolicySizeTrackingEnabled();
+        });
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
index 00702a9..0dde496 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
@@ -5,4 +5,10 @@
   namespace: "enterprise"
   description: "V2 of the policy engine migrations for Android V"
   bug: "289520697"
+}
+flag {
+  name: "device_policy_size_tracking_enabled"
+  namespace: "enterprise"
+  description: "Add feature to track the total policy size and have a max threshold."
+  bug: "281543351"
 }
\ No newline at end of file
diff --git a/services/net/OWNERS b/services/net/OWNERS
index 62c5737..c24680e9 100644
--- a/services/net/OWNERS
+++ b/services/net/OWNERS
@@ -1,2 +1,2 @@
 set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
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
index 823ce45..0547719 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
@@ -112,4 +112,4 @@
         @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
index f085bd7..c44b2c5 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt
@@ -43,8 +43,7 @@
     @Parameterized.Parameter(0) lateinit var action: Action
 
     @Before
-    override fun setUp() {
-        super.setUp()
+    fun setUp() {
         if (action == Action.ON_USER_ADDED) {
             createUserState(USER_ID_NEW)
         }
@@ -881,4 +880,4 @@
         @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
new file mode 100644
index 0000000..e4e3368
--- /dev/null
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
@@ -0,0 +1,430 @@
+/*
+ * 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.PermissionGroupInfo
+import android.content.pm.PermissionInfo
+import com.android.server.permission.access.GetStateScope
+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.testutils.mock
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+class AppIdPermissionPolicyTest : BaseAppIdPermissionPolicyTest() {
+    @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 testOnStorageVolumeMounted_nonSystemAppAfterNonSystemUpdate_remainsRevoked() {
+        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)
+        val oldFlags = PermissionFlags.INSTALL_REVOKED
+        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
+
+        mutateState {
+            with(appIdPermissionPolicy) {
+                onStorageVolumeMounted(null, listOf(installedPackageState.packageName), false)
+            }
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After onStorageVolumeMounted() is called for a non-system app that requests a normal" +
+                " permission with existing INSTALL_REVOKED flag after a non-system-update" +
+                " (such as an OTA update), the actual permission flags should remain revoked." +
+                " The actual permission flags $actualFlags should match the expected flags" +
+                " $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testOnPackageRemoved_packageIsRemoved_permissionDefinitionsAndStatesAreUpdated() {
+        val permissionOwnerPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(
+                PACKAGE_NAME_0,
+                requestedPermissions = setOf(PERMISSION_NAME_0),
+                permissions = listOf(defaultPermission)
+            )
+        )
+        val requestingPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+        )
+        addPackageState(permissionOwnerPackageState)
+        addPackageState(requestingPackageState)
+        addPermission(defaultPermission)
+        val oldFlags = PermissionFlags.INSTALL_GRANTED
+        setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, oldFlags)
+        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
+
+        mutateState {
+            removePackageState(permissionOwnerPackageState)
+            with(appIdPermissionPolicy) {
+                onPackageRemoved(PACKAGE_NAME_0, APP_ID_0)
+            }
+        }
+
+        assertWithMessage(
+            "After onPackageRemoved() is called for a permission owner, the permission" +
+                " definitions owned by this package should be removed"
+        )
+            .that(getPermission(PERMISSION_NAME_0))
+            .isNull()
+
+        val app0ActualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
+        val app0ExpectedNewFlags = 0
+        assertWithMessage(
+            "After onPackageRemoved() is called for a permission owner, the permission states of" +
+                " this app should be trimmed. The actual permission flags $app0ActualFlags should" +
+                " match the expected flags $app0ExpectedNewFlags"
+        )
+            .that(app0ActualFlags)
+            .isEqualTo(app0ExpectedNewFlags)
+
+        val app1ActualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val app1ExpectedNewFlags = PermissionFlags.INSTALL_REVOKED
+        assertWithMessage(
+            "After onPackageRemoved() is called for a permission owner, the permission states of" +
+                " the permission requester should remain unchanged. The actual permission flags" +
+                " $app1ActualFlags should match the expected flags $app1ExpectedNewFlags"
+        )
+            .that(app1ActualFlags)
+            .isEqualTo(app1ExpectedNewFlags)
+    }
+
+    @Test
+    fun testOnPackageInstalled_nonSystemAppIsInstalled_upgradeExemptFlagIsCleared() {
+        val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.UPGRADE_EXEMPT
+        testOnPackageInstalled(
+            oldFlags,
+            permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED
+        ) {}
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.SOFT_RESTRICTED
+        assertWithMessage(
+            "After onPackageInstalled() is called for a non-system app that requests a runtime" +
+                " soft restricted permission, UPGRADE_EXEMPT flag should be removed. The actual" +
+                " permission flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testOnPackageInstalled_systemAppIsInstalled_upgradeExemptFlagIsRetained() {
+        val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.UPGRADE_EXEMPT
+        testOnPackageInstalled(
+            oldFlags,
+            permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED,
+            isInstalledPackageSystem = true
+        ) {}
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After onPackageInstalled() is called for a system app that requests a runtime" +
+                " soft restricted permission, UPGRADE_EXEMPT flag should be retained. The actual" +
+                " permission flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testOnPackageInstalled_requestedPermissionAlsoRequestedBySystemApp_exemptFlagIsRetained() {
+        val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.UPGRADE_EXEMPT
+        testOnPackageInstalled(
+            oldFlags,
+            permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED
+        ) {
+            val systemAppPackageState = mockPackageState(
+                APP_ID_1,
+                mockAndroidPackage(PACKAGE_NAME_2, requestedPermissions = setOf(PERMISSION_NAME_0)),
+                isSystem = true
+            )
+            addPackageState(systemAppPackageState)
+        }
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After onPackageInstalled() is called for a non-system app that requests a runtime" +
+                " soft restricted permission, and that permission is also requested by a system" +
+                " app in the same appId, UPGRADE_EXEMPT flag should be retained. The actual" +
+                " permission flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testOnPackageInstalled_restrictedPermissionsNotExempt_getsRestrictionFlags() {
+        val oldFlags = PermissionFlags.RESTRICTION_REVOKED
+        testOnPackageInstalled(
+            oldFlags,
+            permissionInfoFlags = PermissionInfo.FLAG_HARD_RESTRICTED
+        ) {}
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After onPackageInstalled() is called for a non-system app 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 testOnPackageInstalled_restrictedPermissionsIsExempted_clearsRestrictionFlags() {
+        val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.INSTALLER_EXEMPT
+        testOnPackageInstalled(
+            oldFlags,
+            permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED
+        ) {}
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.INSTALLER_EXEMPT
+        assertWithMessage(
+            "After onPackageInstalled() is called for a non-system app 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)
+    }
+
+    private fun testOnPackageInstalled(
+        oldFlags: Int,
+        permissionInfoFlags: Int = 0,
+        isInstalledPackageSystem: Boolean = false,
+        additionalSetup: () -> Unit
+    ) {
+        val parsedPermission = mockParsedPermission(
+            PERMISSION_NAME_0,
+            PACKAGE_NAME_0,
+            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS,
+            flags = permissionInfoFlags
+        )
+        val permissionOwnerPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
+        )
+        addPackageState(permissionOwnerPackageState)
+        addPermission(parsedPermission)
+
+        additionalSetup()
+
+        mutateState {
+            val installedPackageState = mockPackageState(
+                APP_ID_1,
+                mockAndroidPackage(
+                    PACKAGE_NAME_1,
+                    requestedPermissions = setOf(PERMISSION_NAME_0),
+                ),
+                isSystem = isInstalledPackageSystem,
+            )
+            addPackageState(installedPackageState, newState)
+            setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags, newState)
+            with(appIdPermissionPolicy) {
+                onPackageInstalled(installedPackageState, USER_ID_0)
+            }
+        }
+    }
+
+    @Test
+    fun testOnStateMutated_notEmpty_isCalledForEachListener() {
+        val mockListener = mock<AppIdPermissionPolicy.OnPermissionFlagsChangedListener> {}
+        appIdPermissionPolicy.addOnPermissionFlagsChangedListener(mockListener)
+
+        GetStateScope(oldState).apply {
+            with(appIdPermissionPolicy) {
+                onStateMutated()
+            }
+        }
+
+        verify(mockListener, times(1)).onStateMutated()
+    }
+
+    @Test
+    fun testGetPermissionTrees() {
+        val permissionTrees: IndexedMap<String, Permission>
+        GetStateScope(oldState).apply {
+            with(appIdPermissionPolicy) {
+                permissionTrees = getPermissionTrees()
+            }
+        }
+
+        assertThat(oldState.systemState.permissionTrees).isEqualTo(permissionTrees)
+    }
+
+    @Test
+    fun testFindPermissionTree() {
+        val permissionTree = createSimplePermission(isTree = true)
+        val actualPermissionTree: Permission?
+        oldState.mutateSystemState().mutatePermissionTrees()[PERMISSION_TREE_NAME] = permissionTree
+
+        GetStateScope(oldState).apply {
+            with(appIdPermissionPolicy) {
+                actualPermissionTree = findPermissionTree(PERMISSION_BELONGS_TO_A_TREE)
+            }
+        }
+
+        assertThat(actualPermissionTree).isEqualTo(permissionTree)
+    }
+
+    @Test
+    fun testAddPermissionTree() {
+        val permissionTree = createSimplePermission(isTree = true)
+
+        mutateState {
+            with(appIdPermissionPolicy) {
+                addPermissionTree(permissionTree)
+            }
+        }
+
+        assertThat(newState.systemState.permissionTrees[PERMISSION_TREE_NAME])
+            .isEqualTo(permissionTree)
+    }
+
+    @Test
+    fun testGetPermissionGroups() {
+        val permissionGroups: IndexedMap<String, PermissionGroupInfo>
+        GetStateScope(oldState).apply {
+            with(appIdPermissionPolicy) {
+                permissionGroups = getPermissionGroups()
+            }
+        }
+
+        assertThat(oldState.systemState.permissionGroups).isEqualTo(permissionGroups)
+    }
+
+    @Test
+    fun testGetPermissions() {
+        val permissions: IndexedMap<String, Permission>
+        GetStateScope(oldState).apply {
+            with(appIdPermissionPolicy) {
+                permissions = getPermissions()
+            }
+        }
+
+        assertThat(oldState.systemState.permissions).isEqualTo(permissions)
+    }
+
+    @Test
+    fun testAddPermission() {
+        val permission = createSimplePermission()
+
+        mutateState {
+            with(appIdPermissionPolicy) {
+                addPermission(permission)
+            }
+        }
+
+        assertThat(newState.systemState.permissions[PERMISSION_NAME_0]).isEqualTo(permission)
+    }
+
+    @Test
+    fun testRemovePermission() {
+        val permission = createSimplePermission()
+
+        mutateState {
+            with(appIdPermissionPolicy) {
+                addPermission(permission)
+                removePermission(permission)
+            }
+        }
+
+        assertThat(newState.systemState.permissions[PERMISSION_NAME_0]).isNull()
+    }
+
+    @Test
+    fun testGetUidPermissionFlags() {
+        val uidPermissionFlags: IndexedMap<String, Int>?
+        GetStateScope(oldState).apply {
+            with(appIdPermissionPolicy) {
+                uidPermissionFlags = getUidPermissionFlags(APP_ID_0, USER_ID_0)
+            }
+        }
+
+        assertThat(oldState.userStates[USER_ID_0]!!.appIdPermissionFlags[APP_ID_0])
+            .isEqualTo(uidPermissionFlags)
+    }
+
+    @Test
+    fun testUpdateAndGetPermissionFlags() {
+        val flags = PermissionFlags.INSTALL_GRANTED
+        var actualFlags = 0
+        mutateState {
+            with(appIdPermissionPolicy) {
+                updatePermissionFlags(
+                    APP_ID_0,
+                    USER_ID_0,
+                    PERMISSION_NAME_0,
+                    PermissionFlags.MASK_ALL,
+                    flags
+                )
+                actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
+            }
+        }
+
+        assertThat(actualFlags).isEqualTo(flags)
+    }
+}
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
index 7966c5c..ec84bc3 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt
@@ -34,7 +34,6 @@
 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
@@ -45,10 +44,8 @@
 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
 
@@ -56,7 +53,7 @@
  * Mocking unit test for AppIdPermissionPolicy.
  */
 @RunWith(AndroidJUnit4::class)
-open class BaseAppIdPermissionPolicyTest {
+abstract class BaseAppIdPermissionPolicyTest {
     protected lateinit var oldState: MutableAccessState
     protected lateinit var newState: MutableAccessState
 
@@ -80,7 +77,7 @@
         .build()
 
     @Before
-    open fun setUp() {
+    fun baseSetUp() {
         oldState = MutableAccessState()
         createUserState(USER_ID_0)
         oldState.mutateExternalState().setPackageStates(ArrayMap())
@@ -139,78 +136,6 @@
         }
     }
 
-    @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
      */
@@ -221,6 +146,15 @@
             permissions = listOf(defaultPermissionTree, defaultPermission)
         )
 
+    protected fun createSimplePermission(isTree: Boolean = false): Permission {
+        val parsedPermission = if (isTree) { defaultPermissionTree } else { defaultPermission }
+        val permissionInfo = PackageInfoUtils.generatePermissionInfo(
+            parsedPermission,
+            PackageManager.GET_META_DATA.toLong()
+        )!!
+        return Permission(permissionInfo, true, Permission.TYPE_MANIFEST, APP_ID_0)
+    }
+
     protected inline fun mutateState(action: MutateStateScope.() -> Unit) {
         newState = oldState.toMutable()
         MutateStateScope(oldState, newState).action()
@@ -330,15 +264,26 @@
     ) {
         state.mutateExternalState().apply {
             setPackageStates(
-                packageStates.toMutableMap().apply {
-                    put(packageState.packageName, packageState)
-                }
+                packageStates.toMutableMap().apply { put(packageState.packageName, packageState) }
             )
             mutateAppIdPackageNames().mutateOrPut(packageState.appId) { MutableIndexedListSet() }
                 .add(packageState.packageName)
         }
     }
 
+    protected fun removePackageState(
+        packageState: PackageState,
+        state: MutableAccessState = oldState
+    ) {
+        state.mutateExternalState().apply {
+            setPackageStates(
+                packageStates.toMutableMap().apply { remove(packageState.packageName) }
+            )
+            mutateAppIdPackageNames().mutateOrPut(packageState.appId) { MutableIndexedListSet() }
+                .remove(packageState.packageName)
+        }
+    }
+
     protected fun addDisabledSystemPackageState(
         packageState: PackageState,
         state: MutableAccessState = oldState
@@ -429,6 +374,7 @@
         @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_BELONGS_TO_A_TREE = "permissionTree.permission"
         @JvmStatic protected val PERMISSION_READ_EXTERNAL_STORAGE =
             Manifest.permission.READ_EXTERNAL_STORAGE
         @JvmStatic protected val PERMISSION_POST_NOTIFICATIONS =
diff --git a/services/tests/RemoteProvisioningServiceTests/OWNERS b/services/tests/RemoteProvisioningServiceTests/OWNERS
index 348f940..ea6dc72 100644
--- a/services/tests/RemoteProvisioningServiceTests/OWNERS
+++ b/services/tests/RemoteProvisioningServiceTests/OWNERS
@@ -1 +1 @@
-file:platform/frameworks/base:master:/core/java/android/security/rkp/OWNERS
+file:platform/frameworks/base:main:/core/java/android/security/rkp/OWNERS
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index f57702d..dca69eb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -1851,9 +1851,9 @@
         BrightnessRangeController getBrightnessRangeController(
                 HighBrightnessModeController hbmController, Runnable modeChangeCallback,
                 DisplayDeviceConfig displayDeviceConfig, Handler handler,
-                DisplayManagerFlags flags) {
+                DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
             return new BrightnessRangeController(hbmController, modeChangeCallback,
-                    displayDeviceConfig, mHdrClamper, mFlags);
+                    displayDeviceConfig, mHdrClamper, mFlags, displayToken, info);
         }
 
         @Override
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index 37d966d..c3322ec 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -20,6 +20,12 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.IBinder;
 import android.os.PowerManager;
 
 import androidx.test.filters.SmallTest;
@@ -31,6 +37,7 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -40,7 +47,20 @@
 @SmallTest
 public class HdrClamperTest {
 
-    public static final float FLOAT_TOLERANCE = 0.0001f;
+    private static final float FLOAT_TOLERANCE = 0.0001f;
+    private static final long SEND_TIME_TOLERANCE = 100;
+
+    private static final HdrBrightnessData TEST_HDR_DATA = new HdrBrightnessData(
+            Map.of(500f, 0.6f),
+            /* brightnessIncreaseDebounceMillis= */ 1000,
+            /* brightnessIncreaseDurationMillis= */ 2000,
+            /* brightnessDecreaseDebounceMillis= */ 3000,
+            /* brightnessDecreaseDurationMillis= */4000
+    );
+
+    private static final int WIDTH = 600;
+    private static final int HEIGHT = 800;
+    private static final float MIN_HDR_PERCENT = 0.5f;
 
     @Rule
     public MockitoRule mRule = MockitoJUnit.rule();
@@ -48,17 +68,31 @@
     @Mock
     private BrightnessClamperController.ClamperChangeListener mMockListener;
 
+    @Mock
+    private IBinder mMockBinder;
+
+    @Mock
+    private HdrClamper.Injector mMockInjector;
+
+    @Mock
+    private HdrClamper.HdrLayerInfoListener mMockHdrInfoListener;
+
     OffsettableClock mClock = new OffsettableClock.Stopped();
 
     private final TestHandler mTestHandler = new TestHandler(null, mClock);
 
 
     private HdrClamper mHdrClamper;
-
+    private HdrClamper.HdrListener mHdrChangeListener;
 
     @Before
     public void setUp() {
-        mHdrClamper = new HdrClamper(mMockListener, mTestHandler);
+        when(mMockInjector.getHdrListener(any(), any())).thenReturn(mMockHdrInfoListener);
+        mHdrClamper = new HdrClamper(mMockListener, mTestHandler, mMockInjector);
+        ArgumentCaptor<HdrClamper.HdrListener> listenerCaptor = ArgumentCaptor.forClass(
+                HdrClamper.HdrListener.class);
+        verify(mMockInjector).getHdrListener(listenerCaptor.capture(), eq(mTestHandler));
+        mHdrChangeListener = listenerCaptor.getValue();
         configureClamper();
     }
 
@@ -68,20 +102,23 @@
 
         assertFalse(mTestHandler.hasMessagesOrCallbacks());
         assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+        assertEquals(-1, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
     }
 
     @Test
-    public void testClamper_AmbientLuxChangesBelowLimit() {
+    public void testClamper_AmbientLuxChangesBelowLimit_MaxDecrease() {
         mHdrClamper.onAmbientLuxChange(499);
 
         assertTrue(mTestHandler.hasMessagesOrCallbacks());
         TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek();
-        assertEquals(2000, msgInfo.sendTime);
+        assertSendTime(3000, msgInfo.sendTime);
         assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+        assertEquals(-1, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
 
-        mClock.fastForward(2000);
+        mClock.fastForward(3000);
         mTestHandler.timeAdvance();
         assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0.1f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); // (1-0.6) / 4
     }
 
     @Test
@@ -91,33 +128,65 @@
 
         assertFalse(mTestHandler.hasMessagesOrCallbacks());
         assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+        assertEquals(-1, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
     }
 
     @Test
     public void testClamper_AmbientLuxChangesBelowLimit_ThenSlowlyAboveLimit() {
         mHdrClamper.onAmbientLuxChange(499);
-        mClock.fastForward(2000);
+        mClock.fastForward(3000);
         mTestHandler.timeAdvance();
 
         mHdrClamper.onAmbientLuxChange(500);
 
         assertTrue(mTestHandler.hasMessagesOrCallbacks());
         TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek();
-        assertEquals(3000, msgInfo.sendTime); // 2000 + 1000
+        assertSendTime(4000, msgInfo.sendTime); // 3000 + 1000
 
         mClock.fastForward(1000);
         mTestHandler.timeAdvance();
         assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0.2f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); // (1-0.6) / 2
+    }
+
+    @Test
+    public void testClamper_HdrOff_ThenAmbientLuxChangesBelowLimit() {
+        mHdrChangeListener.onHdrVisible(false);
+        mHdrClamper.onAmbientLuxChange(499);
+
+        assertFalse(mTestHandler.hasMessagesOrCallbacks());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+        assertEquals(-1, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testClamper_HdrOff_ThenAmbientLuxChangesBelowLimit_ThenHdrOn() {
+        mHdrChangeListener.onHdrVisible(false);
+        mHdrClamper.onAmbientLuxChange(499);
+        mHdrChangeListener.onHdrVisible(true);
+
+        assertTrue(mTestHandler.hasMessagesOrCallbacks());
+        TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek();
+        assertSendTime(3000, msgInfo.sendTime);
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+
+        mClock.fastForward(3000);
+        mTestHandler.timeAdvance();
+        assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0.1f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+    }
+
+    // MsgInfo.sendTime is calculated first by adding SystemClock.uptimeMillis()
+    // (in Handler.sendMessageDelayed) and then by subtracting SystemClock.uptimeMillis()
+    // (in TestHandler.sendMessageAtTime, there might be several milliseconds difference between
+    // SystemClock.uptimeMillis() calls, and subtracted value might be greater than added.
+    private static void assertSendTime(long expectedTime, long sendTime) {
+        assertTrue(expectedTime >= sendTime);
+        assertTrue(expectedTime - SEND_TIME_TOLERANCE < sendTime);
     }
 
     private void configureClamper() {
-        HdrBrightnessData data = new HdrBrightnessData(
-                Map.of(500f, 0.6f),
-                /* brightnessIncreaseDebounceMillis= */ 1000,
-                /* brightnessIncreaseDurationMillis= */ 1500,
-                /* brightnessDecreaseDebounceMillis= */ 2000,
-                /* brightnessDecreaseDurationMillis= */2500
-        );
-        mHdrClamper.resetHdrConfig(data);
+        mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, MIN_HDR_PERCENT, mMockBinder);
+        mHdrChangeListener.onHdrVisible(true);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index a0bca3b..24ad976 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -50,7 +50,6 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.platform.test.annotations.FlakyTest;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
 import android.view.InputDevice;
@@ -60,6 +59,7 @@
 import android.view.accessibility.MagnificationAnimationCallback;
 
 import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.FlakyTest;
 
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.LocalServices;
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
new file mode 100644
index 0000000..a7c8a6c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -0,0 +1,890 @@
+/*
+ * 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.
+ */
+
+package com.android.server.companion.virtual;
+
+import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.WindowConfiguration;
+import android.companion.virtual.IVirtualDeviceIntentInterceptor;
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.ArraySet;
+import android.view.Display;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.app.BlockedAppStreamingActivity;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class GenericWindowPolicyControllerTest {
+
+    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
+    private static final int TEST_UID = 1234567;
+    private static final String DISPLAY_CATEGORY = "com.display.category";
+    private static final String NONBLOCKED_APP_PACKAGE_NAME = "com.someapp";
+    private static final String BLOCKED_PACKAGE_NAME = "com.blockedapp";
+    private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
+    private static final String TEST_SITE = "http://test";
+    private static final ComponentName BLOCKED_APP_STREAMING_COMPONENT =
+            new ComponentName("android", BlockedAppStreamingActivity.class.getName());
+    private static final ComponentName BLOCKED_COMPONENT = new ComponentName(BLOCKED_PACKAGE_NAME,
+            BLOCKED_PACKAGE_NAME);
+    private static final ComponentName NONBLOCKED_COMPONENT = new ComponentName(
+            NONBLOCKED_APP_PACKAGE_NAME, NONBLOCKED_APP_PACKAGE_NAME);
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Mock
+    private GenericWindowPolicyController.PipBlockedCallback mPipBlockedCallback;
+    @Mock
+    private VirtualDeviceManager.ActivityListener mActivityListener;
+    @Mock
+    private GenericWindowPolicyController.IntentListenerCallback mIntentListenerCallback;
+    @Mock
+    private GenericWindowPolicyController.ActivityBlockedCallback mActivityBlockedCallback;
+    @Mock
+    private GenericWindowPolicyController.RunningAppsChangedListener mRunningAppsChangedListener;
+    @Mock
+    private GenericWindowPolicyController.SecureWindowCallback mSecureWindowCallback;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void showTasksInHostDeviceRecents() {
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        gwpc.setShowInHostDeviceRecents(true);
+        assertThat(gwpc.canShowTasksInHostDeviceRecents()).isTrue();
+
+        gwpc.setShowInHostDeviceRecents(false);
+        assertThat(gwpc.canShowTasksInHostDeviceRecents()).isFalse();
+    }
+
+    @Test
+    public void containsUid() {
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        assertThat(gwpc.containsUid(TEST_UID)).isFalse();
+
+        gwpc.onRunningAppsChanged(new ArraySet<>(Arrays.asList(TEST_UID)));
+        assertThat(gwpc.containsUid(TEST_UID)).isTrue();
+
+        gwpc.onRunningAppsChanged(new ArraySet<>());
+        assertThat(gwpc.containsUid(TEST_UID)).isFalse();
+    }
+
+    @Test
+    public void isEnteringPipAllowed_falseByDefault() {
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        assertThat(gwpc.isEnteringPipAllowed(TEST_UID)).isFalse();
+        verify(mPipBlockedCallback).onEnteringPipBlocked(TEST_UID);
+    }
+
+    @Test
+    public void isEnteringPipAllowed_dpcSupportsPinned_allowed() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setSupportedWindowingModes(new HashSet<>(
+                Arrays.asList(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
+                        WindowConfiguration.WINDOWING_MODE_PINNED)));
+        assertThat(gwpc.isEnteringPipAllowed(TEST_UID)).isTrue();
+        verify(mPipBlockedCallback, never()).onEnteringPipBlocked(TEST_UID);
+    }
+
+    @Test
+    public void openNonBlockedAppOnVirtualDisplay_isNotBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void activityDoesNotSupportDisplayOnRemoteDevices_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ false,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void openBlockedComponentOnVirtualDisplay_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithBlockedComponent(BLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_PACKAGE_NAME,
+                BLOCKED_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void addActivityPolicyExemption_openBlockedOnVirtualDisplay_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+        gwpc.setActivityLaunchDefaultAllowed(true);
+        gwpc.addActivityPolicyExemption(BLOCKED_COMPONENT);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_PACKAGE_NAME,
+                BLOCKED_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void openNotAllowedComponentOnBlocklistVirtualDisplay_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithAllowedComponent(NONBLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_PACKAGE_NAME,
+                BLOCKED_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void addActivityPolicyExemption_openNotAllowedOnVirtualDisplay_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+        gwpc.setActivityLaunchDefaultAllowed(false);
+        gwpc.addActivityPolicyExemption(NONBLOCKED_COMPONENT);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_PACKAGE_NAME,
+                BLOCKED_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void openAllowedComponentOnBlocklistVirtualDisplay_startsActivity() {
+        GenericWindowPolicyController gwpc = createGwpcWithAllowedComponent(NONBLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void addActivityPolicyExemption_openAllowedOnVirtualDisplay_startsActivity() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+        gwpc.setActivityLaunchDefaultAllowed(false);
+        gwpc.addActivityPolicyExemption(NONBLOCKED_COMPONENT);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_mismatchingUserHandle_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null,
+                /* uid */ UserHandle.PER_USER_RANGE + 1);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_blockedAppStreamingComponent_isNeverBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_APP_STREAMING_COMPONENT.getPackageName(),
+                BLOCKED_APP_STREAMING_COMPONENT.getClassName(),
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_blockedAppStreamingComponentExplicitlyBlocked_isNeverBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithBlockedComponent(
+                BLOCKED_APP_STREAMING_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_APP_STREAMING_COMPONENT.getPackageName(),
+                BLOCKED_APP_STREAMING_COMPONENT.getClassName(),
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_blockedAppStreamingComponentExemptFromStreaming_isNeverBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+        gwpc.setActivityLaunchDefaultAllowed(true);
+        gwpc.addActivityPolicyExemption(BLOCKED_APP_STREAMING_COMPONENT);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_APP_STREAMING_COMPONENT.getPackageName(),
+                BLOCKED_APP_STREAMING_COMPONENT.getClassName(),
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_blockedAppStreamingComponentNotAllowlisted_isNeverBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithAllowedComponent(NONBLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_APP_STREAMING_COMPONENT.getPackageName(),
+                BLOCKED_APP_STREAMING_COMPONENT.getClassName(),
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_blockedAppStreamingComponentNotExemptFromBlocklist_isNeverBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+        gwpc.setActivityLaunchDefaultAllowed(false);
+        gwpc.addActivityPolicyExemption(NONBLOCKED_COMPONENT);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_APP_STREAMING_COMPONENT.getPackageName(),
+                BLOCKED_APP_STREAMING_COMPONENT.getClassName(),
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_customDisplayCategoryMatches_isNotBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithDisplayCategory(DISPLAY_CATEGORY);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ DISPLAY_CATEGORY);
+
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_customDisplayCategoryDoesNotMatch_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithDisplayCategory(DISPLAY_CATEGORY);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ "some.random.category");
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_crossTaskLaunch_fromDefaultDisplay_isNotBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityCanBeLaunched(gwpc, Display.DEFAULT_DISPLAY, true,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_notExplicitlyBlocked_isNotBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationBlockedFor(
+                BLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertActivityCanBeLaunched(gwpc, DISPLAY_ID, true,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_explicitlyBlocked_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationBlockedFor(
+                BLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_PACKAGE_NAME,
+                BLOCKED_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, DISPLAY_ID, true,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_notAllowed_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationAllowed(
+                NONBLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_PACKAGE_NAME,
+                BLOCKED_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, DISPLAY_ID, true,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_allowed_isNotBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationAllowed(
+                NONBLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityCanBeLaunched(gwpc, DISPLAY_ID, true,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_unsupportedWindowingMode_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, DISPLAY_ID, true, WindowConfiguration.WINDOWING_MODE_PINNED,
+                activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_permissionComponent_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithPermissionComponent(BLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_PACKAGE_NAME,
+                BLOCKED_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
+        ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(TEST_UID));
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        gwpc.registerRunningAppsChangedListener(mRunningAppsChangedListener);
+        gwpc.onRunningAppsChanged(uids);
+
+        assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(1);
+        verify(mRunningAppsChangedListener).onRunningAppsChanged(uids);
+    }
+
+    @Test
+    public void onRunningAppsChanged_empty_onDisplayEmpty() {
+        ArraySet<Integer> uids = new ArraySet<>();
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        gwpc.onRunningAppsChanged(uids);
+
+        assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
+        verify(mActivityListener).onDisplayEmpty(DISPLAY_ID);
+    }
+
+    @Test
+    public void noRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() {
+        ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(TEST_UID));
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        gwpc.onRunningAppsChanged(uids);
+
+        assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
+        verify(mRunningAppsChangedListener, never()).onRunningAppsChanged(uids);
+    }
+
+    @Test
+    public void registerUnregisterRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() {
+        ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(TEST_UID));
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        gwpc.registerRunningAppsChangedListener(mRunningAppsChangedListener);
+        gwpc.unregisterRunningAppsChangedListener(mRunningAppsChangedListener);
+        gwpc.onRunningAppsChanged(uids);
+
+        assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
+        verify(mRunningAppsChangedListener, never()).onRunningAppsChanged(uids);
+    }
+
+    @Test
+    public void canActivityBeLaunched_intentInterceptedWhenRegistered_activityNoLaunch()
+            throws RemoteException {
+        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(TEST_SITE));
+
+        IVirtualDeviceIntentInterceptor.Stub interceptor =
+                mock(IVirtualDeviceIntentInterceptor.Stub.class);
+        doNothing().when(interceptor).onIntentIntercepted(any());
+        doReturn(interceptor).when(interceptor).asBinder();
+        doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
+
+        GenericWindowPolicyController gwpc = createGwpc();
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        // register interceptor and intercept intent
+        when(mIntentListenerCallback.shouldInterceptIntent(any(Intent.class))).thenReturn(true);
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
+                .isFalse();
+
+        // unregister interceptor and launch activity
+        when(mIntentListenerCallback.shouldInterceptIntent(any(Intent.class))).thenReturn(false);
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
+                .isTrue();
+    }
+
+    @Test
+    public void canActivityBeLaunched_noMatchIntentFilter_activityLaunches() {
+        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("testing"));
+
+        GenericWindowPolicyController gwpc = createGwpc();
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        // register interceptor with different filter
+        when(mIntentListenerCallback.shouldInterceptIntent(any(Intent.class))).thenReturn(false);
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
+                .isTrue();
+        verify(mIntentListenerCallback).shouldInterceptIntent(any(Intent.class));
+    }
+
+    @Test
+    public void onTopActivitychanged_null_noCallback() {
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        gwpc.onTopActivityChanged(null, 0, 0);
+        verify(mActivityListener, never())
+                .onTopActivityChanged(anyInt(), any(ComponentName.class), anyInt());
+    }
+
+    @Test
+    public void onTopActivitychanged_activityListenerCallbackObserved() {
+        int userId = 1000;
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        gwpc.onTopActivityChanged(BLOCKED_COMPONENT, 0, userId);
+        verify(mActivityListener)
+                .onTopActivityChanged(eq(DISPLAY_ID), eq(BLOCKED_COMPONENT), eq(userId));
+    }
+
+    @Test
+    public void keepActivityOnWindowFlagsChanged_noChange() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0, 0)).isTrue();
+
+        verify(mSecureWindowCallback, never()).onSecureWindowShown(DISPLAY_ID,
+                activityInfo.applicationInfo.uid);
+        verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo);
+    }
+
+    @Test
+    public void keepActivityOnWindowFlagsChanged_flagSecure_isAllowedAfterTM() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, FLAG_SECURE, 0)).isTrue();
+
+        verify(mSecureWindowCallback).onSecureWindowShown(DISPLAY_ID,
+                activityInfo.applicationInfo.uid);
+        verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo);
+    }
+
+    @Test
+    public void keepActivityOnWindowFlagsChanged_systemFlagHideNonSystemOverlayWindows_isAllowedAfterTM() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0,
+                SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)).isTrue();
+
+        verify(mSecureWindowCallback, never()).onSecureWindowShown(DISPLAY_ID,
+                activityInfo.applicationInfo.uid);
+        verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo);
+    }
+
+    @Test
+    public void getCustomHomeComponent_noneSet() {
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        assertThat(gwpc.getCustomHomeComponent()).isNull();
+    }
+
+    @Test
+    public void getCustomHomeComponent_returnsHomeComponent() {
+        GenericWindowPolicyController gwpc = createGwpcWithCustomHomeComponent(
+                NONBLOCKED_COMPONENT);
+
+        assertThat(gwpc.getCustomHomeComponent()).isEqualTo(NONBLOCKED_COMPONENT);
+    }
+
+    private GenericWindowPolicyController createGwpc() {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ new ArraySet<>(),
+                /* crossTaskNavigationAllowedByDefault= */ true,
+                /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ mSecureWindowCallback,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
+    private GenericWindowPolicyController createGwpcWithCustomHomeComponent(
+            ComponentName homeComponent) {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ new ArraySet<>(),
+                /* crossTaskNavigationAllowedByDefault= */ true,
+                /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ null,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ homeComponent);
+    }
+
+    private GenericWindowPolicyController createGwpcWithBlockedComponent(
+            ComponentName blockedComponent) {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ Collections.singleton(blockedComponent),
+                /* crossTaskNavigationAllowedByDefault= */ true,
+                /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ null,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
+    private GenericWindowPolicyController createGwpcWithAllowedComponent(
+            ComponentName allowedComponent) {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ false,
+                /* activityPolicyExemptions= */ Collections.singleton(allowedComponent),
+                /* crossTaskNavigationAllowedByDefault= */ true,
+                /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ null,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
+    private GenericWindowPolicyController createGwpcWithDisplayCategory(
+            String displayCategory) {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ new ArraySet<>(),
+                /* crossTaskNavigationAllowedByDefault= */ true,
+                /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ null,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ Collections.singleton(displayCategory),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
+    private GenericWindowPolicyController createGwpcWithCrossTaskNavigationBlockedFor(
+            ComponentName blockedComponent) {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ new ArraySet<>(),
+                /* crossTaskNavigationAllowedByDefault= */ true,
+                /* crossTaskNavigationExemptions= */ Collections.singleton(blockedComponent),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ null,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
+    private GenericWindowPolicyController createGwpcWithCrossTaskNavigationAllowed(
+            ComponentName allowedComponent) {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ new ArraySet<>(),
+                /* crossTaskNavigationAllowedByDefault= */ false,
+                /* crossTaskNavigationExemptions= */ Collections.singleton(allowedComponent),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ null,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
+    private GenericWindowPolicyController createGwpcWithPermissionComponent(
+            ComponentName permissionComponent) {
+        //TODO instert the component
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ new ArraySet<>(),
+                /* crossTaskNavigationAllowedByDefault= */ false,
+                /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+                /* permissionDialogComponent= */ permissionComponent,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ null,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
+    private Set<UserHandle> getCurrentUserId() {
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        return new ArraySet<>(Arrays.asList(context.getUser()));
+    }
+
+    private ActivityInfo getActivityInfo(
+            String packageName, String name, boolean displayOnRemoteDevices,
+            String requiredDisplayCategory) {
+        return getActivityInfo(packageName, name, displayOnRemoteDevices, requiredDisplayCategory,
+                0);
+    }
+
+    private ActivityInfo getActivityInfo(
+            String packageName, String name, boolean displayOnRemoteDevices,
+            String requiredDisplayCategory, int uid) {
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = uid;
+
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = packageName;
+        activityInfo.name = name;
+        activityInfo.flags = displayOnRemoteDevices
+                ? FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES : FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES;
+        activityInfo.applicationInfo = applicationInfo;
+        activityInfo.requiredDisplayCategory = requiredDisplayCategory;
+        return activityInfo;
+    }
+
+    private void assertActivityCanBeLaunched(GenericWindowPolicyController gwpc,
+            ActivityInfo activityInfo) {
+        assertActivityCanBeLaunched(gwpc, DISPLAY_ID, false,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo);
+    }
+
+    private void assertActivityCanBeLaunched(GenericWindowPolicyController gwpc, int fromDisplay,
+            boolean isNewTask, int windowingMode, ActivityInfo activityInfo) {
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, null, windowingMode, fromDisplay,
+                isNewTask)).isTrue();
+
+        verify(mActivityBlockedCallback, never()).onActivityBlocked(fromDisplay, activityInfo);
+        verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class));
+    }
+
+    private void assertActivityIsBlocked(GenericWindowPolicyController gwpc,
+            ActivityInfo activityInfo) {
+        assertActivityIsBlocked(gwpc, DISPLAY_ID, false,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo);
+    }
+
+    private void assertActivityIsBlocked(GenericWindowPolicyController gwpc, int fromDisplay,
+            boolean isNewTask, int windowingMode, ActivityInfo activityInfo) {
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, null, windowingMode, fromDisplay,
+                isNewTask)).isFalse();
+
+        verify(mActivityBlockedCallback).onActivityBlocked(fromDisplay, activityInfo);
+        verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 41af9e3..263d470 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -24,6 +24,7 @@
 import static android.content.Context.DEVICE_ID_INVALID;
 import static android.content.Intent.ACTION_VIEW;
 import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
+import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -57,6 +58,7 @@
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.flags.Flags;
 import android.companion.virtual.sensor.IVirtualSensorCallback;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorCallback;
@@ -69,6 +71,7 @@
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.hardware.Sensor;
 import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.DisplayManagerInternal;
@@ -100,6 +103,7 @@
 import android.os.RemoteException;
 import android.os.WorkSource;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArraySet;
@@ -211,6 +215,9 @@
     private static final String TEST_SITE = "http://test";
 
     @Rule
+    public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Rule
     public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
             InstrumentationRegistry.getInstrumentation().getUiAutomation(),
             Manifest.permission.CREATE_VIRTUAL_DEVICE);
@@ -298,7 +305,7 @@
         ActivityInfo activityInfo = getActivityInfo(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
-                /* displayOnRemoveDevices= */ true,
+                /* displayOnRemoteDevices= */ true,
                 targetDisplayCategory);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfo, mAssociationInfo.getDisplayName());
@@ -309,12 +316,12 @@
 
 
     private ActivityInfo getActivityInfo(
-            String packageName, String name, boolean displayOnRemoveDevices,
+            String packageName, String name, boolean displayOnRemoteDevices,
             String requiredDisplayCategory) {
         ActivityInfo activityInfo = new ActivityInfo();
         activityInfo.packageName = packageName;
         activityInfo.name = name;
-        activityInfo.flags = displayOnRemoveDevices
+        activityInfo.flags = displayOnRemoteDevices
                 ? FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES : FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES;
         activityInfo.applicationInfo = mApplicationInfoMock;
         activityInfo.requiredDisplayCategory = requiredDisplayCategory;
@@ -328,6 +335,11 @@
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
 
+        mSetFlagsRule.disableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_DYNAMIC_POLICY);
+        mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_VDM_CUSTOM_HOME);
+
         doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
         doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
         doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
@@ -1417,7 +1429,7 @@
         ActivityInfo activityInfo = getActivityInfo(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfo, mAssociationInfo.getDisplayName());
@@ -1438,7 +1450,51 @@
         ActivityInfo activityInfo = getActivityInfo(
                 PERMISSION_CONTROLLER_PACKAGE_NAME,
                 PERMISSION_CONTROLLER_PACKAGE_NAME,
-                /* displayOnRemoveDevices */  false,
+                /* displayOnRemoteDevices */  false,
+                /* targetDisplayCategory */ null);
+        Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+                activityInfo, mAssociationInfo.getDisplayName());
+        gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
+
+        verify(mContext).startActivityAsUser(argThat(intent ->
+                intent.filterEquals(blockedAppIntent)), any(), any());
+    }
+
+    @Test
+    public void openPermissionControllerOnVirtualDisplay_displayOnRemoteDevices_startsWhenFlagIsEnabled() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
+                DISPLAY_ID_1);
+        doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+        ActivityInfo activityInfo = getActivityInfo(
+                PERMISSION_CONTROLLER_PACKAGE_NAME,
+                PERMISSION_CONTROLLER_PACKAGE_NAME,
+                /* displayOnRemoveDevices */ true,
+                /* targetDisplayCategory */ null);
+        Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+                activityInfo, mAssociationInfo.getDisplayName());
+        gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
+
+        verify(mContext, never()).startActivityAsUser(argThat(intent ->
+                intent.filterEquals(blockedAppIntent)), any(), any());
+    }
+
+    @Test
+    public void openPermissionControllerOnVirtualDisplay_dontDisplayOnRemoteDevices_startsWhenFlagIsEnabled() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
+                DISPLAY_ID_1);
+        doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+        ActivityInfo activityInfo = getActivityInfo(
+                PERMISSION_CONTROLLER_PACKAGE_NAME,
+                PERMISSION_CONTROLLER_PACKAGE_NAME,
+                /* displayOnRemoveDevices */ false,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfo, mAssociationInfo.getDisplayName());
@@ -1459,7 +1515,7 @@
         ActivityInfo activityInfo = getActivityInfo(
                 SETTINGS_PACKAGE_NAME,
                 SETTINGS_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfo, mAssociationInfo.getDisplayName());
@@ -1480,7 +1536,7 @@
         ActivityInfo activityInfo = getActivityInfo(
                 VENDING_PACKAGE_NAME,
                 VENDING_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfo, mAssociationInfo.getDisplayName());
@@ -1501,7 +1557,7 @@
         ActivityInfo activityInfo = getActivityInfo(
                 GOOGLE_DIALER_PACKAGE_NAME,
                 GOOGLE_DIALER_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfo, mAssociationInfo.getDisplayName());
@@ -1522,7 +1578,7 @@
         ActivityInfo activityInfo = getActivityInfo(
                 GOOGLE_MAPS_PACKAGE_NAME,
                 GOOGLE_MAPS_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfo, mAssociationInfo.getDisplayName());
@@ -1562,6 +1618,54 @@
     }
 
     @Test
+    public void canActivityBeLaunched_permissionDialog_flagDisabled_isBlocked() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+        VirtualDeviceParams params = new VirtualDeviceParams.Builder().build();
+        mDeviceImpl.close();
+        mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
+        doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
+                DISPLAY_ID_1);
+        ComponentName permissionComponent = getPermissionDialogComponent();
+        ActivityInfo activityInfo = getActivityInfo(
+                permissionComponent.getPackageName(),
+                permissionComponent.getClassName(),
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, null,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
+                .isFalse();
+
+        Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+                activityInfo, mAssociationInfo.getDisplayName());
+        verify(mContext).startActivityAsUser(argThat(intent ->
+                intent.filterEquals(blockedAppIntent)), any(), any());
+    }
+
+    @Test
+    public void canActivityBeLaunched_permissionDialog_flagEnabled_isStreamed() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+        VirtualDeviceParams params = new VirtualDeviceParams.Builder().build();
+        mDeviceImpl.close();
+        mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
+
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
+                DISPLAY_ID_1);
+        ComponentName permissionComponent = getPermissionDialogComponent();
+        ActivityInfo activityInfo = getActivityInfo(
+                permissionComponent.getPackageName(),
+                permissionComponent.getClassName(),
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, null,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
+                .isTrue();
+    }
+
+    @Test
     public void canActivityBeLaunched_activityCanLaunch() {
         Intent intent = new Intent(ACTION_VIEW, Uri.parse(TEST_SITE));
         addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
@@ -1570,7 +1674,7 @@
         ActivityInfo activityInfo = getActivityInfo(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
         assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
                 WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
@@ -1594,7 +1698,7 @@
         ActivityInfo activityInfo = getActivityInfo(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
 
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_VIEW);
@@ -1637,7 +1741,7 @@
         ActivityInfo activityInfo = getActivityInfo(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
 
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_VIEW);
@@ -1758,6 +1862,13 @@
                 NONBLOCKED_APP_PACKAGE_NAME);
     }
 
+    private ComponentName getPermissionDialogComponent() {
+        Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
+        PackageManager packageManager = mContext.getPackageManager();
+        intent.setPackage(packageManager.getPermissionControllerPackageName());
+        return intent.resolveActivity(packageManager);
+    }
+
     /** Helper class to drop permissions temporarily and restore them at the end of a test. */
     static final class DropShellPermissionsTemporarily implements AutoCloseable {
         DropShellPermissionsTemporarily() {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
index c65452a..07dd59d2 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
@@ -32,11 +32,12 @@
 import android.companion.virtual.flags.Flags;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -49,6 +50,10 @@
     private static final int VIRTUAL_DEVICE_ID = 42;
     private static final String PERSISTENT_ID = "persistentId";
     private static final String DEVICE_NAME = "VirtualDeviceName";
+    private static final String DISPLAY_NAME = "DisplayName";
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Mock
     private IVirtualDevice mVirtualDevice;
@@ -87,7 +92,8 @@
     @Test
     public void parcelable_shouldRecreateSuccessfully() {
         VirtualDevice originalDevice =
-                new VirtualDevice(mVirtualDevice, VIRTUAL_DEVICE_ID, PERSISTENT_ID, DEVICE_NAME);
+                new VirtualDevice(mVirtualDevice, VIRTUAL_DEVICE_ID, PERSISTENT_ID, DEVICE_NAME,
+                        DISPLAY_NAME);
         Parcel parcel = Parcel.obtain();
         originalDevice.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
@@ -96,11 +102,13 @@
         assertThat(device.getDeviceId()).isEqualTo(VIRTUAL_DEVICE_ID);
         assertThat(device.getPersistentDeviceId()).isEqualTo(PERSISTENT_ID);
         assertThat(device.getName()).isEqualTo(DEVICE_NAME);
+        assertThat(device.getDisplayName().toString()).isEqualTo(DISPLAY_NAME);
     }
 
-    @RequiresFlagsEnabled(Flags.FLAG_VDM_PUBLIC_APIS)
     @Test
     public void virtualDevice_getDisplayIds() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
+
         VirtualDevice virtualDevice =
                 new VirtualDevice(
                         mVirtualDevice, VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null);
@@ -113,9 +121,10 @@
         assertThat(virtualDevice.getDisplayIds()).isEqualTo(displayIds);
     }
 
-    @RequiresFlagsEnabled(Flags.FLAG_VDM_PUBLIC_APIS)
     @Test
     public void virtualDevice_hasCustomSensorSupport() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
+
         VirtualDevice virtualDevice =
                 new VirtualDevice(
                         mVirtualDevice, VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 1c48b8a..ef5270e 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -82,6 +82,7 @@
                         /* activityPolicyExemptions= */ new ArraySet<>(),
                         /* crossTaskNavigationAllowedByDefault= */ true,
                         /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+                        /* permissionDialogComponent */ null,
                         /* activityListener= */ null,
                         /* pipBlockedCallback= */ null,
                         /* activityBlockedCallback= */ null,
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 f65cb93..40ac7b1c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2855,14 +2855,14 @@
                 .setTask(sourceRecord.getTask()).build();
         secondRecord.showStartingWindow(null /* prev */, true /* newTask */, false,
                 true /* startActivity */, sourceRecord);
-        assertTrue(secondRecord.mAllowIconSplashScreen);
+        assertFalse(secondRecord.mSplashScreenStyleSolidColor);
         secondRecord.onStartingWindowDrawn();
 
         final ActivityRecord finalRecord = new ActivityBuilder(mAtm)
                 .setTask(sourceRecord.getTask()).build();
         finalRecord.showStartingWindow(null /* prev */, true /* newTask */, false,
                 true /* startActivity */, secondRecord);
-        assertFalse(finalRecord.mAllowIconSplashScreen);
+        assertTrue(finalRecord.mSplashScreenStyleSolidColor);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 25619b9..4c25a4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -91,6 +91,7 @@
 import java.util.Random;
 import java.util.Set;
 import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * Build/Install/Run:
@@ -563,6 +564,38 @@
     }
 
     @Test
+    public void testTasksWithCorrectOrderOfLastActiveTime() {
+        mRecentTasks.setOnlyTestVisibleRange();
+        mRecentTasks.unloadUserDataFromMemoryLocked(TEST_USER_0_ID);
+
+        // Setup some tasks for the user
+        mTaskPersister.mUserTaskIdsOverride = new SparseBooleanArray();
+        mTaskPersister.mUserTaskIdsOverride.put(1, true);
+        mTaskPersister.mUserTaskIdsOverride.put(2, true);
+        mTaskPersister.mUserTaskIdsOverride.put(3, true);
+        mTaskPersister.mUserTasksOverride = new ArrayList<>();
+        mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask1").build());
+        mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask2").build());
+        mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask3").build());
+
+        // Assert no user tasks are initially loaded
+        assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).hasLength(0);
+
+        // Load tasks
+        mRecentTasks.loadUserRecentsLocked(TEST_USER_0_ID);
+        assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).asList().contains(TEST_USER_0_ID);
+
+        // Sort the time descendingly so the order should be in-sync with task recency (most
+        // recent to least recent)
+        List<Task> tasksSortedByTime = mRecentTasks.getRawTasks().stream()
+                .sorted((o1, o2) -> Long.compare(o2.lastActiveTime, o1.lastActiveTime))
+                .collect(Collectors.toList());
+
+        assertTrue("Task order is not in sync with its recency",
+                mRecentTasks.getRawTasks().equals(tasksSortedByTime));
+    }
+
+    @Test
     public void testOrderedIteration() {
         mRecentTasks.setOnlyTestVisibleRange();
         Task task1 = createTaskBuilder(".Task1").build();
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index f7719a6..95cbff9 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -38,6 +38,10 @@
     private static final boolean SKIP_METHOD_LOG = "1".equals(System.getenv(
             "HOSTTEST_SKIP_METHOD_LOG"));
 
+    /** If true, we won't print class load log. */
+    private static final boolean SKIP_CLASS_LOG = "1".equals(System.getenv(
+            "HOSTTEST_SKIP_CLASS_LOG"));
+
     /** If true, we won't perform non-stub method direct call check. */
     private static final boolean SKIP_NON_STUB_METHOD_CHECK = "1".equals(System.getenv(
             "HOSTTEST_SKIP_NON_STUB_METHOD_CHECK"));
@@ -57,17 +61,34 @@
     }
 
     /**
-     * Called from methods with FilterPolicy.Log.
+     * Trampoline method for method-call-hook.
+     */
+    public static void callMethodCallHook(
+            Class<?> methodClass,
+            String methodName,
+            String methodDescriptor,
+            String callbackMethod
+    ) {
+        callStaticMethodByName(callbackMethod, methodClass, methodName, methodDescriptor);
+    }
+
+    /**
+     * I can be used as
+     * {@code --default-method-call-hook
+     * com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall}.
+     *
+     * It logs every single methods called.
      */
     public static void logMethodCall(
-            String methodClass,
+            Class<?> methodClass,
             String methodName,
             String methodDescriptor
     ) {
         if (SKIP_METHOD_LOG) {
             return;
         }
-        logPrintStream.println("# " + methodClass + "." + methodName + methodDescriptor);
+        logPrintStream.println("# method called: " + methodClass.getCanonicalName() + "."
+                + methodName + methodDescriptor);
     }
 
     private static final StackWalker sStackWalker =
@@ -146,15 +167,19 @@
         logPrintStream.println("! Class loaded: " + loadedClass.getCanonicalName()
                 + " calling hook " + callbackMethod);
 
+        callStaticMethodByName(callbackMethod, loadedClass);
+    }
+
+    private static void callStaticMethodByName(String classAndMethodName, Object... args) {
         // Forward the call to callbackMethod.
-        final int lastPeriod = callbackMethod.lastIndexOf(".");
-        final String className = callbackMethod.substring(0, lastPeriod);
-        final String methodName = callbackMethod.substring(lastPeriod + 1);
+        final int lastPeriod = classAndMethodName.lastIndexOf(".");
+        final String className = classAndMethodName.substring(0, lastPeriod);
+        final String methodName = classAndMethodName.substring(lastPeriod + 1);
 
         if (lastPeriod < 0 || className.isEmpty() || methodName.isEmpty()) {
             throw new HostTestException(String.format(
                     "Unable to find class load hook: malformed method name \"%s\"",
-                    callbackMethod));
+                    classAndMethodName));
         }
 
         Class<?> clazz = null;
@@ -169,13 +194,19 @@
                     "Unable to find class load hook: Class %s must be public", className));
         }
 
+        Class<?>[] argTypes = new Class[args.length];
+        for (int i = 0; i < args.length; i++) {
+            argTypes[i] = args[i].getClass();
+        }
+
         Method method = null;
         try {
-            method = clazz.getMethod(methodName, Class.class);
+            method = clazz.getMethod(methodName, argTypes);
         } catch (Exception e) {
             throw new HostTestException(String.format(
                     "Unable to find class load hook: class %s doesn't have method %s"
-                    + " (method must take exactly one parameter of type Class, and public static)",
+                            + " (method must take exactly one parameter of type Class,"
+                            + " and public static)",
                     className,
                     methodName), e);
         }
@@ -186,7 +217,7 @@
                     methodName, className));
         }
         try {
-            method.invoke(null, loadedClass);
+            method.invoke(null, args);
         } catch (Exception e) {
             throw new HostTestException(String.format(
                     "Unable to invoke class load hook %s.%s",
@@ -194,4 +225,18 @@
                     methodName), e);
         }
     }
+
+    /**
+     * I can be used as
+     * {@code --default-class-load-hook
+     * com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded}.
+     *
+     * It logs every loaded class.
+     */
+    public static void logClassLoaded(Class<?> clazz) {
+        if (SKIP_CLASS_LOG) {
+            return;
+        }
+        logPrintStream.println("# class loaded: " + clazz.getCanonicalName());
+    }
 }
diff --git a/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt b/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt
index 828d2a3..3f87527 100644
--- a/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt
+++ b/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt
@@ -6,8 +6,10 @@
 --enable-non-stub-method-check
 # --no-non-stub-method-check
 
-# --enable-method-logging
-
+#--default-method-call-hook
+#    com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+#--default-class-load-hook
+#    com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
 
 # Standard annotations.
 # Note, each line is a single argument, so we need newlines after each `--xxx-annotation`.
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 8db4b69..7531759 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -17,6 +17,7 @@
 
 import com.android.hoststubgen.asm.ClassNodes
 import com.android.hoststubgen.filters.AnnotationBasedFilter
+import com.android.hoststubgen.filters.DefaultHookInjectingFilter
 import com.android.hoststubgen.filters.ClassWidePolicyPropagatingFilter
 import com.android.hoststubgen.filters.ConstantFilter
 import com.android.hoststubgen.filters.FilterPolicy
@@ -156,22 +157,29 @@
         // This is used when a member (methods, fields, nested classes) don't get any polices
         // from upper filters. e.g. when a method has no annotations, then this filter will apply
         // the class-wide policy, if any. (if not, we'll fall back to the above filter.)
-        val classWidePropagator = ClassWidePolicyPropagatingFilter(filter)
+        filter = ClassWidePolicyPropagatingFilter(filter)
+
+        // Inject default hooks from options.
+        filter = DefaultHookInjectingFilter(
+            options.defaultClassLoadHook,
+            options.defaultMethodCallHook,
+            filter
+        )
 
         // Next, Java annotation based filter.
         filter = AnnotationBasedFilter(
-                errors,
-                allClasses,
-                options.stubAnnotations,
-                options.keepAnnotations,
-                options.stubClassAnnotations,
-                options.keepClassAnnotations,
-                options.throwAnnotations,
-                options.removeAnnotations,
-                options.substituteAnnotations,
-                options.nativeSubstituteAnnotations,
-                options.classLoadHookAnnotations,
-                classWidePropagator
+            errors,
+            allClasses,
+            options.stubAnnotations,
+            options.keepAnnotations,
+            options.stubClassAnnotations,
+            options.keepClassAnnotations,
+            options.throwAnnotations,
+            options.removeAnnotations,
+            options.substituteAnnotations,
+            options.nativeSubstituteAnnotations,
+            options.classLoadHookAnnotations,
+            filter
         )
 
         // Next, "text based" filter, which allows to override polices without touching
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index 9a54ecf..bbb7dab 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -48,6 +48,9 @@
         var nativeSubstituteAnnotations: MutableSet<String> = mutableSetOf(),
         var classLoadHookAnnotations: MutableSet<String> = mutableSetOf(),
 
+        var defaultClassLoadHook: String? = null,
+        var defaultMethodCallHook: String? = null,
+
         var intersectStubJars: MutableSet<String> = mutableSetOf(),
 
         var policyOverrideFile: String? = null,
@@ -63,8 +66,6 @@
         var enablePreTrace: Boolean = false,
         var enablePostTrace: Boolean = false,
 
-        var enableMethodLogging: Boolean = false,
-
         var enableNonStubMethodCallDetection: Boolean = true,
 ) {
     companion object {
@@ -151,6 +152,12 @@
                         ret.classLoadHookAnnotations +=
                             ensureUniqueAnnotation(ai.nextArgRequired(arg))
 
+                    "--default-class-load-hook" ->
+                        ret.defaultClassLoadHook = ai.nextArgRequired(arg)
+
+                    "--default-method-call-hook" ->
+                        ret.defaultMethodCallHook = ai.nextArgRequired(arg)
+
                     "--intersect-stub-jar" ->
                         ret.intersectStubJars += ai.nextArgRequired(arg).ensureFileExists()
 
@@ -167,9 +174,6 @@
                     "--enable-post-trace" -> ret.enablePostTrace = true
                     "--no-post-trace" -> ret.enablePostTrace = false
 
-                    "--enable-method-logging" -> ret.enableMethodLogging = true
-                    "--no-method-logging" -> ret.enableMethodLogging = false
-
                     "--enable-non-stub-method-check" -> ret.enableNonStubMethodCallDetection = true
                     "--no-non-stub-method-check" -> ret.enableNonStubMethodCallDetection = false
 
@@ -290,6 +294,8 @@
               substituteAnnotations=$substituteAnnotations,
               nativeSubstituteAnnotations=$nativeSubstituteAnnotations,
               classLoadHookAnnotations=$classLoadHookAnnotations,
+              defaultClassLoadHook=$defaultClassLoadHook,
+              defaultMethodCallHook=$defaultMethodCallHook,
               intersectStubJars=$intersectStubJars,
               policyOverrideFile=$policyOverrideFile,
               defaultPolicy=$defaultPolicy,
@@ -299,7 +305,6 @@
               enableClassChecker=$enableClassChecker,
               enablePreTrace=$enablePreTrace,
               enablePostTrace=$enablePostTrace,
-              enableMethodLogging=$enableMethodLogging,
               enableNonStubMethodCallDetection=$enableNonStubMethodCallDetection,
             }
             """.trimIndent()
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
index 9fbd6d0..7d7852a 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
@@ -31,3 +31,23 @@
     // Remove surrounding whitespace.
     return uncommented.trim()
 }
+
+fun <T> addLists(a: List<T>, b: List<T>): List<T> {
+    if (a.isEmpty()) {
+        return b
+    }
+    if (b.isEmpty()) {
+        return a
+    }
+    return a + b
+}
+
+fun <T> addNonNullElement(a: List<T>, b: T?): List<T> {
+    if (b == null) {
+        return a
+    }
+    if (a.isEmpty()) {
+        return listOf(b)
+    }
+    return a + b
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
index 454569d..3f492e8 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
@@ -19,6 +19,7 @@
 import com.android.hoststubgen.HostStubGenErrors
 import com.android.hoststubgen.HostStubGenInternalException
 import com.android.hoststubgen.InvalidAnnotationException
+import com.android.hoststubgen.addNonNullElement
 import com.android.hoststubgen.asm.ClassNodes
 import com.android.hoststubgen.asm.findAnnotationValueAsString
 import com.android.hoststubgen.asm.findAnyAnnotation
@@ -253,14 +254,14 @@
         return null
     }
 
-    override fun getClassLoadHook(className: String): String? {
-        classes.getClass(className).let { cn ->
+    override fun getClassLoadHooks(className: String): List<String> {
+        val e = classes.getClass(className).let { cn ->
             findAnyAnnotation(classLoadHookAnnotations,
                 cn.visibleAnnotations, cn.invisibleAnnotations)?.let { an ->
-                return getAnnotationField(an, "value")?.toHumanReadableMethodName()
+                getAnnotationField(an, "value")?.toHumanReadableMethodName()
             }
         }
-        return null
+        return addNonNullElement(super.getClassLoadHooks(className), e)
     }
 
     private data class MethodKey(val name: String, val desc: String)
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt
new file mode 100644
index 0000000..d771003
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.hoststubgen.filters
+
+import com.android.hoststubgen.addLists
+
+class DefaultHookInjectingFilter(
+    defaultClassLoadHook: String?,
+    defaultMethodCallHook: String?,
+    fallback: OutputFilter
+) : DelegatingFilter(fallback) {
+    /**
+     * Create a List containing a single element [e], if e != null. Otherwise, returns
+     * an empty list.
+     */
+    private fun toSingleList(e: String?): List<String> {
+        if (e == null) {
+            return emptyList()
+        }
+        return listOf(e)
+    }
+
+    private val defaultClassLoadHookAsList: List<String> = toSingleList(defaultClassLoadHook)
+    private val defaultMethodCallHookAsList: List<String> = toSingleList(defaultMethodCallHook)
+
+    override fun getClassLoadHooks(className: String): List<String> {
+        return addLists(super.getClassLoadHooks(className), defaultClassLoadHookAsList)
+    }
+
+    override fun getMethodCallHooks(
+        className: String,
+        methodName: String,
+        descriptor: String
+    ): List<String> {
+        return addLists(
+            super.getMethodCallHooks(className, methodName, descriptor),
+            defaultMethodCallHookAsList,
+            )
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt
index f0763c4..45f61c5 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt
@@ -66,7 +66,15 @@
         return fallback.getNativeSubstitutionClass(className)
     }
 
-    override fun getClassLoadHook(className: String): String? {
-        return fallback.getClassLoadHook(className)
+    override fun getClassLoadHooks(className: String): List<String> {
+        return fallback.getClassLoadHooks(className)
+    }
+
+    override fun getMethodCallHooks(
+        className: String,
+        methodName: String,
+        descriptor: String
+    ): List<String> {
+        return fallback.getMethodCallHooks(className, methodName, descriptor)
     }
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
index f3551d4..5659a35 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
@@ -16,6 +16,7 @@
 package com.android.hoststubgen.filters
 
 import com.android.hoststubgen.UnknownApiException
+import com.android.hoststubgen.addNonNullElement
 import com.android.hoststubgen.asm.ClassNodes
 import com.android.hoststubgen.asm.toHumanReadableClassName
 import com.android.hoststubgen.asm.toHumanReadableMethodName
@@ -127,9 +128,9 @@
         mNativeSubstitutionClasses[getClassKey(from)] = to.toHumanReadableClassName()
     }
 
-    override fun getClassLoadHook(className: String): String? {
-        return mClassLoadHooks[getClassKey(className)]
-            ?: super.getClassLoadHook(className)
+    override fun getClassLoadHooks(className: String): List<String> {
+        return addNonNullElement(super.getClassLoadHooks(className),
+            mClassLoadHooks[getClassKey(className)])
     }
 
     fun setClassLoadHook(className: String, methodName: String) {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt
index 392ee4b..3df16ff 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt
@@ -71,11 +71,22 @@
     }
 
     /**
-     * Return a "class load hook" method name for a given class.
+     * Return a "class load hook" class name for a given class.
      *
      * (which corresponds to @HostSideTestClassLoadHook of the standard annotations.)
      */
-    open fun getClassLoadHook(className: String): String? {
-        return null
+    open fun getClassLoadHooks(className: String): List<String> {
+        return emptyList()
+    }
+
+    /**
+     * Return the "method call hook" class name.
+     *
+     * The class has to have a function with the following signature:
+     * `public static void onMethodCalled(Class<?> clazz, String name, String descriptor)`.
+     */
+    open fun getMethodCallHooks(className: String, methodName: String, descriptor: String):
+            List<String> {
+        return emptyList()
     }
 }
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
index ac06886..57b6689 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -45,7 +45,7 @@
         return policy.needsInImpl
     }
 
-    private var classLoadHookMethod: String? = null
+    private var classLoadHooks: List<String> = emptyList()
 
     override fun visit(
         version: Int,
@@ -57,22 +57,22 @@
     ) {
         super.visit(version, access, name, signature, superName, interfaces)
 
-        classLoadHookMethod = filter.getClassLoadHook(currentClassName)
+        classLoadHooks = filter.getClassLoadHooks(currentClassName)
 
         // classLoadHookMethod is non-null, then we need to inject code to call it
         // in the class initializer.
         // If the target class already has a class initializer, then we need to inject code to it.
         // Otherwise, we need to create one.
 
-        classLoadHookMethod?.let { callback ->
-            log.d("  ClassLoadHook: $callback")
+        if (classLoadHooks.isNotEmpty()) {
+            log.d("  ClassLoadHooks: $classLoadHooks")
             if (!classes.hasClassInitializer(currentClassName)) {
-                injectClassLoadHook(callback)
+                injectClassLoadHook()
             }
         }
     }
 
-    private fun injectClassLoadHook(callback: String) {
+    private fun injectClassLoadHook() {
         writeRawMembers {
             // Create a class initializer to call onClassLoaded().
             // Each class can only have at most one class initializer, but the base class
@@ -87,7 +87,7 @@
                 // Method prologue
                 mv.visitCode()
 
-                writeClassLoadHookCall(mv)
+                writeClassLoadHookCalls(mv)
                 mv.visitInsn(Opcodes.RETURN)
 
                 // Method epilogue
@@ -97,21 +97,23 @@
         }
     }
 
-    private fun writeClassLoadHookCall(mv: MethodVisitor) {
-        // First argument: the class type.
-        mv.visitLdcInsn(Type.getType("L" + currentClassName + ";"))
+    private fun writeClassLoadHookCalls(mv: MethodVisitor) {
+        classLoadHooks.forEach { classLoadHook ->
+            // First argument: the class type.
+            mv.visitLdcInsn(Type.getType("L" + currentClassName + ";"))
 
-        // Second argument: method name
-        mv.visitLdcInsn(classLoadHookMethod)
+            // Second argument: method name
+            mv.visitLdcInsn(classLoadHook)
 
-        // Call HostTestUtils.onClassLoaded().
-        mv.visitMethodInsn(
-            Opcodes.INVOKESTATIC,
-            HostTestUtils.CLASS_INTERNAL_NAME,
-            "onClassLoaded",
-            "(Ljava/lang/Class;Ljava/lang/String;)V",
-            false
-        )
+            // Call HostTestUtils.onClassLoaded().
+            mv.visitMethodInsn(
+                Opcodes.INVOKESTATIC,
+                HostTestUtils.CLASS_INTERNAL_NAME,
+                "onClassLoaded",
+                "(Ljava/lang/Class;Ljava/lang/String;)V",
+                false
+            )
+        }
     }
 
     override fun updateAccessFlags(
@@ -138,20 +140,22 @@
         var innerVisitor = superVisitor
 
         //  If method logging is enabled, inject call to the logging method.
-        if (options.enableMethodLogging) {
-            innerVisitor = LogInjectingMethodAdapter(
-                    access,
-                    name,
-                    descriptor,
-                    signature,
-                    exceptions,
-                    innerVisitor,
-                    )
+        val methodCallHooks = filter.getMethodCallHooks(currentClassName, name, descriptor)
+        if (methodCallHooks.isNotEmpty()) {
+            innerVisitor = MethodCallHookInjectingAdapter(
+                access,
+                name,
+                descriptor,
+                signature,
+                exceptions,
+                innerVisitor,
+                methodCallHooks,
+                )
         }
 
         // If this class already has a class initializer and a class load hook is needed, then
         // we inject code.
-        if (classLoadHookMethod != null &&
+        if (classLoadHooks.isNotEmpty() &&
             name == CLASS_INITIALIZER_NAME &&
             descriptor == CLASS_INITIALIZER_DESC) {
             innerVisitor = ClassLoadHookInjectingMethodAdapter(
@@ -283,29 +287,37 @@
     }
 
     /**
-     * A method adapter that injects a call to HostTestUtils.logMethodCall() to every method.
+     * Inject calls to the method call hooks.
      *
      * Note, when the target method is a constructor, it may contain calls to `super(...)` or
      * `this(...)`. The logging code will be injected *before* such calls.
      */
-    private inner class LogInjectingMethodAdapter(
+    private inner class MethodCallHookInjectingAdapter(
             access: Int,
             val name: String,
             val descriptor: String,
             signature: String?,
             exceptions: Array<String>?,
-            next: MethodVisitor?
+            next: MethodVisitor?,
+            val hooks: List<String>,
     ) : MethodVisitor(OPCODE_VERSION, next) {
         override fun visitCode() {
             super.visitCode()
-            visitLdcInsn(currentClassName)
-            visitLdcInsn(name)
-            visitLdcInsn(descriptor)
-            visitMethodInsn(Opcodes.INVOKESTATIC,
+
+            hooks.forEach { hook ->
+                mv.visitLdcInsn(Type.getType("L" + currentClassName + ";"))
+                visitLdcInsn(name)
+                visitLdcInsn(descriptor)
+                visitLdcInsn(hook)
+
+                visitMethodInsn(
+                    Opcodes.INVOKESTATIC,
                     HostTestUtils.CLASS_INTERNAL_NAME,
-                    "logMethodCall",
-                    "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
-                    false)
+                    "callMethodCallHook",
+                    "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
+                    false
+                )
+            }
         }
     }
 
@@ -323,7 +335,7 @@
         override fun visitCode() {
             super.visitCode()
 
-            writeClassLoadHookCall(this)
+            writeClassLoadHookCalls(this)
         }
     }
 
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp b/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
index 8c76a61..05d6a43 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
@@ -53,6 +53,46 @@
     ],
 }
 
+// Same as "hoststubgen-test-tiny-framework-host", but with more options, to test more hoststubgen
+// features.
+java_genrule_host {
+    name: "hoststubgen-test-tiny-framework-host-ext",
+    defaults: ["hoststubgen-command-defaults"],
+    cmd: hoststubgen_common_options +
+        "--in-jar $(location :hoststubgen-test-tiny-framework) " +
+        "--policy-override-file $(location policy-override-tiny-framework.txt) " +
+
+        // More options.
+        "--default-method-call-hook com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall " +
+        "--default-class-load-hook com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded ",
+    srcs: [
+        ":hoststubgen-test-tiny-framework",
+        "policy-override-tiny-framework.txt",
+    ],
+}
+
+java_genrule_host {
+    name: "hoststubgen-test-tiny-framework-host-ext-stub",
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":hoststubgen-test-tiny-framework-host-ext{host_stub.jar}",
+    ],
+    out: [
+        "host_stub.jar",
+    ],
+}
+
+java_genrule_host {
+    name: "hoststubgen-test-tiny-framework-host-ext-impl",
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":hoststubgen-test-tiny-framework-host-ext{host_impl.jar}",
+    ],
+    out: [
+        "host_impl.jar",
+    ],
+}
+
 // Compile the test jar, using 2 rules.
 // 1. Build the test against the stub.
 java_library_host {
@@ -123,6 +163,30 @@
     visibility: ["//visibility:private"],
 }
 
+java_genrule_host {
+    name: "hoststubgen-test-tiny-framework-host-ext-stub-dump",
+    defaults: ["hoststubgen-jar-dump-defaults"],
+    srcs: [
+        ":hoststubgen-test-tiny-framework-host-ext-stub",
+    ],
+    out: [
+        "12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+java_genrule_host {
+    name: "hoststubgen-test-tiny-framework-host-ext-impl-dump",
+    defaults: ["hoststubgen-jar-dump-defaults"],
+    srcs: [
+        ":hoststubgen-test-tiny-framework-host-ext-impl",
+    ],
+    out: [
+        "13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt",
+    ],
+    visibility: ["//visibility:private"],
+}
+
 // Run it with `atest`. Compare the dump of the jar files to the golden output.
 python_test_host {
     name: "tiny-framework-dump-test",
@@ -136,6 +200,8 @@
         "hoststubgen-test-tiny-framework-host-stub-dump",
         "hoststubgen-test-tiny-framework-host-impl-dump",
         "hoststubgen-test-tiny-framework-orig-dump",
+        "hoststubgen-test-tiny-framework-host-ext-stub-dump",
+        "hoststubgen-test-tiny-framework-host-ext-impl-dump",
     ],
     test_suites: ["general-tests"],
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/diff-and-update-golden.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
index 4d58869..639fb16 100755
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
@@ -112,7 +112,7 @@
 
 if (( $three_way )) ; then
   echo "# Running 3-way diff with meld..."
-  run meld ${files[*]} &
+  run meld ${files[0]} ${files[1]} ${files[2]} &
 fi
 
 if (( $two_way )) ; then
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
new file mode 100644
index 0000000..43ceec4
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -0,0 +1,837 @@
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
+  Compiled from "TinyFrameworkCallerCheck.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 4
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl();
+    descriptor: ()V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=1, args_size=1
+         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
+         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:
+      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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class
+  Compiled from "TinyFrameworkCallerCheck.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 5
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         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
+         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
+         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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class
+  Compiled from "TinyFrameworkClassAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 5, attributes: 3
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         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:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         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:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         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
+         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
+         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:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+}
+SourceFile: "TinyFrameworkClassAnnotations.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestStub
+  x: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class
+  Compiled from "TinyFrameworkClassClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 3, methods: 8, attributes: 3
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int remove;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         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
+         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
+         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
+         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
+         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
+         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
+         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
+         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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
+  Compiled from "TinyFrameworkClassLoadHook.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 3, attributes: 3
+  public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
+    descriptor: Ljava/util/Set;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/Set<Ljava/lang/Class<*>;>;
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook();
+    descriptor: ()V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=1, args_size=1
+         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
+         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 {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.class
+  Compiled from "TinyFrameworkClassWithInitializer.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 3
+  public static boolean sInitialized;
+    descriptor: Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         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
+         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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
+  Compiled from "TinyFrameworkExceptionTester.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         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
+         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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
+  Compiled from "TinyFrameworkForTextPolicy.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 5, attributes: 2
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         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
+         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
+         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
+         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
+         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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
+  Compiled from "TinyFrameworkNative.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 5, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         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
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+
+  public static int nativeAddTwo_should_be_like_this(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         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
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+
+  public static long nativeLongPlus_should_be_like_this(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=4, args_size=2
+         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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+  x: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestNativeSubstitutionClass(
+      value="TinyFrameworkNative_host"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 1, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 1, attributes: 5
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 5
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         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
+         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:
+  public static #x= #x of #x;            // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass extends com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  interfaces: 0, fields: 0, methods: 1, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 4, attributes: 5
+  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         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
+         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();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         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 {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         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
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  public static #x= #x of #x;           // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  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;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public #x= #x of #x;                  // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
new file mode 100644
index 0000000..874789e
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -0,0 +1,2348 @@
+## Class: android/hosttest/annotation/HostSideTestClassLoadHook.class
+  Compiled from "HostSideTestClassLoadHook.java"
+public interface android.hosttest.annotation.HostSideTestClassLoadHook extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestClassLoadHook
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 2, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestClassLoadHook
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  public abstract java.lang.String value();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+}
+SourceFile: "HostSideTestClassLoadHook.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestKeep.class
+  Compiled from "HostSideTestKeep.java"
+public interface android.hosttest.annotation.HostSideTestKeep extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestKeep
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestKeep
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+}
+SourceFile: "HostSideTestKeep.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  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]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestNativeSubstitutionClass.class
+  Compiled from "HostSideTestNativeSubstitutionClass.java"
+public interface android.hosttest.annotation.HostSideTestNativeSubstitutionClass extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestNativeSubstitutionClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 2, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestNativeSubstitutionClass
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  public abstract java.lang.String value();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+}
+SourceFile: "HostSideTestNativeSubstitutionClass.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestRemove.class
+  Compiled from "HostSideTestRemove.java"
+public interface android.hosttest.annotation.HostSideTestRemove extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestRemove
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestRemove
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+}
+SourceFile: "HostSideTestRemove.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  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]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestStub.class
+  Compiled from "HostSideTestStub.java"
+public interface android.hosttest.annotation.HostSideTestStub extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestStub
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestStub
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+}
+SourceFile: "HostSideTestStub.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  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]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestSubstitute.class
+  Compiled from "HostSideTestSubstitute.java"
+public interface android.hosttest.annotation.HostSideTestSubstitute extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestSubstitute
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 2, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestSubstitute
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  public abstract java.lang.String suffix();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+}
+SourceFile: "HostSideTestSubstitute.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestThrow.class
+  Compiled from "HostSideTestThrow.java"
+public interface android.hosttest.annotation.HostSideTestThrow extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestThrow
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestThrow
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+}
+SourceFile: "HostSideTestThrow.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  x: #x(#x=[e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestWholeClassKeep.class
+  Compiled from "HostSideTestWholeClassKeep.java"
+public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestWholeClassKeep
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestWholeClassKeep
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+}
+SourceFile: "HostSideTestWholeClassKeep.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestWholeClassStub.class
+  Compiled from "HostSideTestWholeClassStub.java"
+public interface android.hosttest.annotation.HostSideTestWholeClassStub extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestWholeClassStub
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestWholeClassStub
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+}
+SourceFile: "HostSideTestWholeClassStub.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
+  Compiled from "TinyFrameworkCallerCheck.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 4, attributes: 4
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl();
+    descriptor: ()V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl;
+
+  public static int getOneKeep();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+         x: ldc           #x                 // String getOneKeep
+         x: ldc           #x                 // String ()I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static int getOneStub();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+         x: ldc           #x                 // String getOneStub
+         x: ldc           #x                 // String ()I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iconst_1
+        x: ireturn
+      LineNumberTable:
+    RuntimeInvisibleAnnotations:
+      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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class
+  Compiled from "TinyFrameworkCallerCheck.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 4, attributes: 5
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         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.TinyFrameworkCallerCheck();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck;
+
+  public static int getOne_withCheck();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+         x: ldc           #x                 // String getOne_withCheck
+         x: ldc           #x                 // String ()I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I
+        x: ireturn
+      LineNumberTable:
+
+  public static int getOne_noCheck();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+         x: ldc           #x                 // String getOne_noCheck
+         x: ldc           #x                 // String ()I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class
+  Compiled from "TinyFrameworkClassAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 8, attributes: 3
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         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=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           11      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                 // String addOne
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: iload_1
+        x: invokevirtual #x                 // Method addOneInner:(I)I
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+           11       6     1 value   I
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public int addOneInner(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                 // String addOneInner
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           26       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+           26       4     1 value   I
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                 // String addTwo
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iload_1
+        x: iconst_2
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+           11       4     1 value   I
+
+  public static int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                 // String nativeAddThree
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iload_0
+        x: iconst_3
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0 value   I
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                 // String unsupportedMethod
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+
+  public java.lang.String visibleButUsesUnsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                 // String visibleButUsesUnsupportedMethod
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+}
+SourceFile: "TinyFrameworkClassAnnotations.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestStub
+  x: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class
+  Compiled from "TinyFrameworkClassClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 3, methods: 9, attributes: 3
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int remove;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         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.TinyFrameworkClassClassWideAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           11      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
+         x: ldc           #x                 // String addOne
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: iload_1
+        x: invokevirtual #x                 // Method addOneInner:(I)I
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+           11       6     1 value   I
+
+  public int addOneInner(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
+         x: ldc           #x                 // String addOneInner
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iload_1
+        x: iconst_1
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+           11       4     1 value   I
+
+  public void toBeRemoved(java.lang.String);
+    descriptor: (Ljava/lang/String;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
+         x: ldc           #x                 // String toBeRemoved
+         x: ldc           #x                 // String (Ljava/lang/String;)V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           11       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+           11       8     1   foo   Ljava/lang/String;
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
+         x: ldc           #x                 // String addTwo
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iload_1
+        x: iconst_2
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+           11       4     1 value   I
+
+  public static int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
+         x: ldc           #x                 // String nativeAddThree
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iload_0
+        x: iconst_3
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0 value   I
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
+         x: ldc           #x                 // String unsupportedMethod
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: ldc           #x                 // String This value shouldn\'t be seen on the host side.
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+
+  public java.lang.String visibleButUsesUnsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
+         x: ldc           #x                 // String visibleButUsesUnsupportedMethod
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+}
+SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
+  Compiled from "TinyFrameworkClassLoadHook.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 3, attributes: 3
+  public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
+    descriptor: Ljava/util/Set;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/Set<Ljava/lang/Class<*>;>;
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook();
+    descriptor: ()V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook;
+
+  public static void onClassLoaded(java.lang.Class<?>);
+    descriptor: (Ljava/lang/Class;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
+         x: ldc           #x                 // String onClassLoaded
+         x: ldc           #x                 // String (Ljava/lang/Class;)V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           11      11     0 clazz   Ljava/lang/Class;
+      LocalVariableTypeTable:
+        Start  Length  Slot  Name   Signature
+           11      11     0 clazz   Ljava/lang/Class<*>;
+    Signature: #x                          // (Ljava/lang/Class<*>;)V
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
+         x: ldc           #x                 // String <clinit>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
+        x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+        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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.class
+  Compiled from "TinyFrameworkClassWithInitializer.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 3
+  public static boolean sInitialized;
+    descriptor: Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer;
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer
+         x: ldc           #x                 // String <clinit>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer
+        x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+        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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
+  Compiled from "TinyFrameworkExceptionTester.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 3
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         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.TinyFrameworkExceptionTester();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester;
+
+  public static int testException();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
+         x: ldc           #x                 // String testException
+         x: ldc           #x                 // String ()I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+            11    21    21   Class java/lang/Exception
+      StackMapTable: number_of_entries = 1
+        frame_type = 85 /* same_locals_1_stack_item */
+          stack = [ class java/lang/Exception ]
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           22      11     0     e   Ljava/lang/Exception;
+}
+SourceFile: "TinyFrameworkExceptionTester.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
+  Compiled from "TinyFrameworkForTextPolicy.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 8, attributes: 2
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         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=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           11      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String addOne
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: iload_1
+        x: invokevirtual #x                 // Method addOneInner:(I)I
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+           11       6     1 value   I
+
+  public int addOneInner(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String addOneInner
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           26       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+           26       4     1 value   I
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String addTwo
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iload_1
+        x: iconst_2
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+           11       4     1 value   I
+
+  public static int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String nativeAddThree
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iload_0
+        x: iconst_3
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0 value   I
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String unsupportedMethod
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String visibleButUsesUnsupportedMethod
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+}
+SourceFile: "TinyFrameworkForTextPolicy.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
+  Compiled from "TinyFrameworkNative.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 6, attributes: 3
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         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.TinyFrameworkNative();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+
+  public static int nativeAddTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=1, args_size=1
+         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=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeAddTwo_should_be_like_this
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           11       5     0   arg   I
+
+  public static long nativeLongPlus(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=4, args_size=2
+         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
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeLongPlus_should_be_like_this
+         x: ldc           #x                 // String (JJ)J
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           11       6     0  arg1   J
+           11       6     2  arg2   J
+}
+SourceFile: "TinyFrameworkNative.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+  x: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestNativeSubstitutionClass(
+      value="TinyFrameworkNative_host"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.class
+  Compiled from "TinyFrameworkNative_host.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 4, attributes: 3
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         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.TinyFrameworkNative_host();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host;
+
+  public static int nativeAddTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String nativeAddTwo
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           26       4     0   arg   I
+
+  public static long nativeLongPlus(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=4, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String nativeLongPlus
+         x: ldc           #x                 // String (JJ)J
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           26       4     0  arg1   J
+           26       4     2  arg2   J
+}
+SourceFile: "TinyFrameworkNative_host.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 1, methods: 4, attributes: 6
+  final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
+
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0000)
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           11      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+           11      10     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Object;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+EnclosingMethod: #x.#x                 // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 4, attributes: 6
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2();
+    descriptor: ()V
+    flags: (0x0000)
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Object;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+EnclosingMethod: #x.#x                 // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 1, methods: 4, attributes: 6
+  final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
+
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0000)
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           11      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+           11      10     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Object;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.getSupplier
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 4, attributes: 6
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4();
+    descriptor: ()V
+    flags: (0x0000)
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Object;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.getSupplier_static
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         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.TinyFrameworkNestedClasses$BaseClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (I)V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           11      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass;
+           11      10     1     x   I
+}
+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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 2, attributes: 5
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
+
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         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.TinyFrameworkNestedClasses$InnerClass(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           11      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass;
+           11      15     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+}
+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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 4, attributes: 6
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1();
+    descriptor: ()V
+    flags: (0x0000)
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           26       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Object;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+}
+InnerClasses:
+  public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass.getSupplier_static
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 3, attributes: 5
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         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.TinyFrameworkNestedClasses$StaticNestedClass();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           11      11     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass;
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+         x: ldc           #x                 // String getSupplier_static
+         x: ldc           #x                 // String ()Ljava/util/function/Supplier;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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;>;
+}
+InnerClasses:
+  public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass extends com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  interfaces: 0, fields: 0, methods: 2, attributes: 4
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         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.TinyFrameworkNestedClasses$SubClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (I)V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass;
+           11       6     1     x   I
+}
+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:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 4, attributes: 5
+  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           11      17     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+
+  public java.util.function.Supplier<java.lang.Integer> getSupplier();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+         x: ldc           #x                 // String getSupplier
+         x: ldc           #x                 // String ()Ljava/util/function/Supplier;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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
+           11       9     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+         x: ldc           #x                 // String getSupplier_static
+         x: ldc           #x                 // String ()Ljava/util/function/Supplier;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        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;>;
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+         x: ldc           #x                 // String <clinit>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+        x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+        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:
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  public static #x= #x of #x;           // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  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;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public #x= #x of #x;                  // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
index 6bc0ddb..7600942 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -34,6 +34,7 @@
 
 run ./hoststubgen/test-framework/run-test-without-atest.sh
 run ./hoststubgen/test-tiny-framework/run-test-manually.sh
+run atest tiny-framework-dump-test
 run ./scripts/build-framework-hostside-jars-and-extract.sh
 
 # This script is already broken on goog/master
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
index cbbf91b4..758de4d 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
@@ -131,7 +131,7 @@
                 priority = 6,
                 severity = Severity.ERROR,
                 implementation = Implementation(
-                        EnforcePermissionDetector::class.java,
+                        EnforcePermissionHelperDetector::class.java,
                         EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
                 )
         )