Merge "Move IActivityTaskManager::getTasks binder call to background" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 48d6392..8591a9c 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -77,6 +77,7 @@
         "camera_platform_flags_core_java_lib",
         "com.android.hardware.input-aconfig-java",
         "com.android.input.flags-aconfig-java",
+        "com.android.internal.compat.flags-aconfig-java",
         "com.android.internal.foldables.flags-aconfig-java",
         "com.android.internal.pm.pkg.component.flags-aconfig-java",
         "com.android.media.flags.bettertogether-aconfig-java",
@@ -663,6 +664,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Platform Compat
+java_aconfig_library {
+    name: "com.android.internal.compat.flags-aconfig-java",
+    aconfig_declarations: "compat_logging_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Multi user
 aconfig_declarations {
     name: "android.multiuser.flags-aconfig",
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index 0104ee1..ace56d4 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -20,6 +20,7 @@
     ],
 
     libs: [
+        "androidx.annotation_annotation",
         "app-compat-annotations",
         "error_prone_annotations",
         "framework",
diff --git a/core/api/current.txt b/core/api/current.txt
index c810691..e0b919a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8084,7 +8084,7 @@
     method public CharSequence getStartUserSessionMessage(@NonNull android.content.ComponentName);
     method @Deprecated public boolean getStorageEncryption(@Nullable android.content.ComponentName);
     method public int getStorageEncryptionStatus();
-    method @FlaggedApi("android.app.admin.flags.esim_management_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionsIds();
+    method @FlaggedApi("android.app.admin.flags.esim_management_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionIds();
     method @Nullable public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy();
     method @Nullable public android.os.PersistableBundle getTransferOwnershipBundle();
     method @Nullable public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(@Nullable android.content.ComponentName, @NonNull android.content.ComponentName);
@@ -9602,8 +9602,8 @@
     method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
     method @FlaggedApi("android.appwidget.flags.generated_previews") @Nullable public android.widget.RemoteViews getWidgetPreview(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle, int);
     method public boolean isRequestPinAppWidgetSupported();
-    method public void notifyAppWidgetViewDataChanged(int[], int);
-    method public void notifyAppWidgetViewDataChanged(int, int);
+    method @Deprecated public void notifyAppWidgetViewDataChanged(int[], int);
+    method @Deprecated public void notifyAppWidgetViewDataChanged(int, int);
     method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
     method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
     method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int);
@@ -28054,7 +28054,7 @@
     method public void sendSigningResult(@NonNull String, @NonNull byte[]);
     method public void sendTrackInfoList(@Nullable java.util.List<android.media.tv.TvTrackInfo>);
     method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.TvAdCallback);
-    method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
+    method public void setOnUnhandledInputEventListener(@NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
     method public boolean setTvView(@Nullable android.media.tv.TvView);
     method public void startAdService();
     method public void stopAdService();
@@ -52447,8 +52447,8 @@
     method public final void cancelPendingInputEvents();
     method public boolean checkInputConnectionProxy(android.view.View);
     method public void clearAnimation();
-    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearCredentialManagerRequest();
     method public void clearFocus();
+    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearPendingCredentialRequest();
     method public void clearViewTranslationCallback();
     method public static int combineMeasuredStates(int, int);
     method protected int computeHorizontalScrollExtent();
@@ -52557,8 +52557,6 @@
     method @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public final int getContentSensitivity();
     method @UiContext public final android.content.Context getContext();
     method protected android.view.ContextMenu.ContextMenuInfo getContextMenuInfo();
-    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getCredentialManagerCallback();
-    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.credentials.GetCredentialRequest getCredentialManagerRequest();
     method public final boolean getDefaultFocusHighlightEnabled();
     method public static int getDefaultSize(int, int);
     method public android.view.Display getDisplay();
@@ -52643,6 +52641,8 @@
     method public int getPaddingTop();
     method public final android.view.ViewParent getParent();
     method public android.view.ViewParent getParentForAccessibility();
+    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getPendingCredentialCallback();
+    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.credentials.GetCredentialRequest getPendingCredentialRequest();
     method public float getPivotX();
     method public float getPivotY();
     method public android.view.PointerIcon getPointerIcon();
@@ -52943,7 +52943,6 @@
     method public void setContentDescription(CharSequence);
     method @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public final void setContentSensitivity(int);
     method public void setContextClickable(boolean);
-    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setCredentialManagerRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
     method public void setDefaultFocusHighlightEnabled(boolean);
     method @Deprecated public void setDrawingCacheBackgroundColor(@ColorInt int);
     method @Deprecated public void setDrawingCacheEnabled(boolean);
@@ -53022,6 +53021,7 @@
     method public void setOverScrollMode(int);
     method public void setPadding(int, int, int, int);
     method public void setPaddingRelative(int, int, int, int);
+    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setPendingCredentialRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
     method public void setPivotX(float);
     method public void setPivotY(float);
     method public void setPointerIcon(android.view.PointerIcon);
@@ -53825,10 +53825,10 @@
     method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearCredentialManagerRequest();
     method @Nullable public abstract android.view.autofill.AutofillId getAutofillId();
     method public abstract int getChildCount();
-    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getCredentialManagerCallback();
-    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.credentials.GetCredentialRequest getCredentialManagerRequest();
     method public abstract android.os.Bundle getExtras();
     method public abstract CharSequence getHint();
+    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getPendingCredentialCallback();
+    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.credentials.GetCredentialRequest getPendingCredentialRequest();
     method public abstract CharSequence getText();
     method public abstract int getTextSelectionEnd();
     method public abstract int getTextSelectionStart();
@@ -53851,7 +53851,6 @@
     method public abstract void setClickable(boolean);
     method public abstract void setContentDescription(CharSequence);
     method public abstract void setContextClickable(boolean);
-    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setCredentialManagerRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
     method public abstract void setDataIsSensitive(boolean);
     method public abstract void setDimens(int, int, int, int, int, int);
     method public abstract void setElevation(float);
@@ -53870,6 +53869,7 @@
     method public void setMaxTextLength(int);
     method public void setMinTextEms(int);
     method public abstract void setOpaque(boolean);
+    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setPendingCredentialRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
     method public void setReceiveContentMimeTypes(@Nullable String[]);
     method public abstract void setSelected(boolean);
     method public abstract void setText(CharSequence);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b73f199..5645119 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1306,6 +1306,7 @@
 
   public class DevicePolicyManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPrecondition(@NonNull String, @NonNull String);
+    method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
     method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account);
@@ -1342,7 +1343,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException;
     method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean);
-    method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>);
+    method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
     method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int);
@@ -6260,7 +6261,7 @@
     method public void addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.OnCompleteListener);
     method public void addOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener);
     method public void close();
-    method @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
+    method @Deprecated @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
     method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramInfos(@NonNull android.hardware.radio.ProgramSelector.Identifier);
     method public void registerListCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.ListCallback);
     method public void registerListCallback(@NonNull android.hardware.radio.ProgramList.ListCallback);
@@ -6312,7 +6313,7 @@
     field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
     field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5
     field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa
-    field public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
+    field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
     field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9
     field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3
     field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; // 0xf
@@ -6320,8 +6321,8 @@
     field @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4
     field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0
     field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2
-    field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
-    field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
+    field @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
+    field @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
     field public static final int IDENTIFIER_TYPE_VENDOR_END = 1999; // 0x7cf
     field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf
     field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8
@@ -6374,7 +6375,7 @@
     field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8
     field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7
     field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9
-    field public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
+    field @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
     field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_AM = 11; // 0xb
     field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_FM = 10; // 0xa
     field public static final int CONFIG_FORCE_DIGITAL = 3; // 0x3
@@ -14213,7 +14214,6 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
     method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, @NonNull android.os.UserHandle);
-    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(@Nullable android.telecom.PhoneAccountHandle);
     field public static final String ACTION_CURRENT_TTY_MODE_CHANGED = "android.telecom.action.CURRENT_TTY_MODE_CHANGED";
@@ -15393,7 +15393,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String);
     method public boolean needsOtaServiceProvisioning();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
-    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.DUMP) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticData);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.READ_DROPBOX_DATA) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticData);
     method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerCarrierPrivilegesCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesCallback);
diff --git a/core/java/Android.bp b/core/java/Android.bp
index ab1c9a4..4f96206 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -167,6 +167,9 @@
         "com/android/internal/logging/UiEventLoggerImpl.java",
         ":statslog-framework-java-gen",
     ],
+    libs: [
+        "androidx.annotation_annotation",
+    ],
     static_libs: ["modules-utils-uieventlogger-interface"],
 }
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1d39186..ae5cacd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -974,6 +974,7 @@
         ContentCaptureOptions contentCaptureOptions;
 
         long[] disabledCompatChanges;
+        long[] mLoggableCompatChanges;
 
         SharedMemory mSerializedSystemFontMap;
 
@@ -1283,6 +1284,7 @@
                 AutofillOptions autofillOptions,
                 ContentCaptureOptions contentCaptureOptions,
                 long[] disabledCompatChanges,
+                long[] loggableCompatChanges,
                 SharedMemory serializedSystemFontMap,
                 long startRequestedElapsedTime,
                 long startRequestedUptime) {
@@ -1337,6 +1339,7 @@
             data.autofillOptions = autofillOptions;
             data.contentCaptureOptions = contentCaptureOptions;
             data.disabledCompatChanges = disabledCompatChanges;
+            data.mLoggableCompatChanges = loggableCompatChanges;
             data.mSerializedSystemFontMap = serializedSystemFontMap;
             data.startRequestedElapsedTime = startRequestedElapsedTime;
             data.startRequestedUptime = startRequestedUptime;
@@ -4073,6 +4076,13 @@
                     ActivityManager.getService().waitForNetworkStateUpdate(mNetworkBlockSeq);
                     mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
                 } catch (RemoteException ignored) {}
+                if (Flags.clearDnsCacheOnNetworkRulesUpdate()) {
+                    // InetAddress will cache UnknownHostException failures. If the rules got
+                    // updated and the app has network access now, we need to clear the negative
+                    // cache to ensure valid dns queries can work immediately.
+                    // TODO: b/329133769 - Clear only the negative cache once it is available.
+                    InetAddress.clearDnsCache();
+                }
             }
         }
     }
@@ -7123,7 +7133,7 @@
         Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis(),
                 data.startRequestedElapsedTime, data.startRequestedUptime);
 
-        AppCompatCallbacks.install(data.disabledCompatChanges);
+        AppCompatCallbacks.install(data.disabledCompatChanges, data.mLoggableCompatChanges);
         // Let libcore handle any compat changes after installing the list of compat changes.
         AppSpecializationHooks.handleCompatChangesBeforeBindingApplication();
 
diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java
index 134cef5..f2debfc 100644
--- a/core/java/android/app/AppCompatCallbacks.java
+++ b/core/java/android/app/AppCompatCallbacks.java
@@ -30,41 +30,59 @@
  */
 public final class AppCompatCallbacks implements Compatibility.BehaviorChangeDelegate {
     private final long[] mDisabledChanges;
+    private final long[] mLoggableChanges;
     private final ChangeReporter mChangeReporter;
 
     /**
-     * Install this class into the current process.
+     * Install this class into the current process using the disabled and loggable changes lists.
      *
      * @param disabledChanges Set of compatibility changes that are disabled for this process.
+     * @param loggableChanges Set of compatibility changes that we want to log.
      */
-    public static void install(long[] disabledChanges) {
-        Compatibility.setBehaviorChangeDelegate(new AppCompatCallbacks(disabledChanges));
+    public static void install(long[] disabledChanges, long[] loggableChanges) {
+        Compatibility.setBehaviorChangeDelegate(
+                new AppCompatCallbacks(disabledChanges, loggableChanges));
     }
 
-    private AppCompatCallbacks(long[] disabledChanges) {
+    private AppCompatCallbacks(long[] disabledChanges, long[] loggableChanges) {
         mDisabledChanges = Arrays.copyOf(disabledChanges, disabledChanges.length);
+        mLoggableChanges = Arrays.copyOf(loggableChanges, loggableChanges.length);
         Arrays.sort(mDisabledChanges);
-        mChangeReporter = new ChangeReporter(
-                ChangeReporter.SOURCE_APP_PROCESS);
+        Arrays.sort(mLoggableChanges);
+        mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_APP_PROCESS);
+    }
+
+    /**
+     * Helper to determine if a list contains a changeId.
+     *
+     * @param list to search through
+     * @param changeId for which to search in the list
+     * @return true if the given changeId is found in the provided array.
+     */
+    private boolean changeIdInChangeList(long[] list, long changeId) {
+        return Arrays.binarySearch(list, changeId) >= 0;
     }
 
     public void onChangeReported(long changeId) {
-        reportChange(changeId, ChangeReporter.STATE_LOGGED);
+        boolean isLoggable = changeIdInChangeList(mLoggableChanges, changeId);
+        reportChange(changeId, ChangeReporter.STATE_LOGGED, isLoggable);
     }
 
     public boolean isChangeEnabled(long changeId) {
-        if (Arrays.binarySearch(mDisabledChanges, changeId) < 0) {
-            // Not present in the disabled array
-            reportChange(changeId, ChangeReporter.STATE_ENABLED);
+        boolean isEnabled = !changeIdInChangeList(mDisabledChanges, changeId);
+        boolean isLoggable = changeIdInChangeList(mLoggableChanges, changeId);
+        if (isEnabled) {
+            // Not present in the disabled changeId array
+            reportChange(changeId, ChangeReporter.STATE_ENABLED, isLoggable);
             return true;
         }
-        reportChange(changeId, ChangeReporter.STATE_DISABLED);
+        reportChange(changeId, ChangeReporter.STATE_DISABLED, isLoggable);
         return false;
     }
 
-    private void reportChange(long changeId, int state) {
+    private void reportChange(long changeId, int state, boolean isLoggable) {
         int uid = Process.myUid();
-        mChangeReporter.reportChange(uid, changeId, state);
+        mChangeReporter.reportChange(uid, changeId, state, isLoggable);
     }
 
 }
diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java
index 7c8b0fd..ef6982e 100644
--- a/core/java/android/app/DreamManager.java
+++ b/core/java/android/app/DreamManager.java
@@ -185,6 +185,22 @@
     }
 
     /**
+     * Whether dreaming can start given user settings and the current dock/charge state.
+     *
+     * @hide
+     */
+    @UserHandleAware
+    @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE)
+    public boolean canStartDreaming(boolean isScreenOn) {
+        try {
+            return mService.canStartDreaming(isScreenOn);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return false;
+    }
+
+    /**
      * Returns whether the device is Dreaming.
      *
      * <p> This is only used for testing the dream service APIs.
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index a04620c..251e4e8 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -90,7 +90,7 @@
             in CompatibilityInfo compatInfo, in Map services,
             in Bundle coreSettings, in String buildSerial, in AutofillOptions autofillOptions,
             in ContentCaptureOptions contentCaptureOptions, in long[] disabledCompatChanges,
-            in SharedMemory serializedSystemFontMap,
+            in long[] loggableCompatChanges, in SharedMemory serializedSystemFontMap,
             long startRequestedElapsedTime, long startRequestedUptime);
     void runIsolatedEntryPoint(in String entryPoint, in String[] entryPointArgs);
     void scheduleExit();
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b25ebf6..620bbaf 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -14090,9 +14090,7 @@
         try {
             return mService.isAuditLogEnabled(mContext.getPackageName());
         } catch (RemoteException re) {
-            re.rethrowFromSystemServer();
-            // unreachable
-            return false;
+            throw re.rethrowFromSystemServer();
         }
     }
 
@@ -14102,8 +14100,8 @@
      * is enforced by the caller. Disabling the policy clears the callback. Each time a new callback
      * is set, it will first be invoked with all the audit log events available at the time.
      *
-     * @param callback callback to invoke when new audit log events become available or {@code null}
-     *                 to clear the callback.
+     * @param callback The callback to invoke when new audit log events become available.
+     * @param executor The executor through which the callback should be invoked.
      * @hide
      */
     @SystemApi
@@ -14111,11 +14109,10 @@
     @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
     public void setAuditLogEventCallback(
             @NonNull @CallbackExecutor Executor executor,
-            @Nullable Consumer<List<SecurityEvent>> callback) {
+            @NonNull Consumer<List<SecurityEvent>> callback) {
         throwIfParentInstance("setAuditLogEventCallback");
-        final IAuditLogEventsCallback wrappedCallback = callback == null
-                ? null
-                : new IAuditLogEventsCallback.Stub() {
+        final IAuditLogEventsCallback wrappedCallback =
+                new IAuditLogEventsCallback.Stub() {
                     @Override
                     public void onNewAuditLogEvents(List<SecurityEvent> events) {
                         executor.execute(() -> callback.accept(events));
@@ -14124,7 +14121,25 @@
         try {
             mService.setAuditLogEventsCallback(mContext.getPackageName(), wrappedCallback);
         } catch (RemoteException re) {
-            re.rethrowFromSystemServer();
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Clears audit log event callback. If a callback was set previously, it may still get invoked
+     * after this call returns if it was already scheduled.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
+    @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+    public void clearAuditLogEventCallback() {
+        throwIfParentInstance("clearAuditLogEventCallback");
+        try {
+            mService.setAuditLogEventsCallback(mContext.getPackageName(), null);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
         }
     }
 
@@ -17442,24 +17457,24 @@
     }
 
     /**
-     * Returns the subscription ids of all subscriptions which was downloaded by the calling
+     * Returns the subscription ids of all subscriptions which were downloaded by the calling
      * admin.
      *
      * <p> This returns only the subscriptions which were downloaded by the calling admin via
      *      {@link android.telephony.euicc.EuiccManager#downloadSubscription}.
-     *      If a susbcription is returned by this method then in it subject to management controls
+     *      If a subscription is returned by this method then in it subject to management controls
      *      and cannot be removed by users.
      *
      * <p> Callable by device owners and profile owners.
      *
-     * @throws SecurityException if the caller is not authorized to call this method
-     * @return ids of all managed subscriptions currently downloaded by an admin on the device
+     * @throws SecurityException if the caller is not authorized to call this method.
+     * @return ids of all managed subscriptions currently downloaded by an admin on the device.
      */
     @FlaggedApi(FLAG_ESIM_MANAGEMENT_ENABLED)
     @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS)
     @NonNull
-    public Set<Integer> getSubscriptionsIds() {
-        throwIfParentInstance("getSubscriptionsIds");
+    public Set<Integer> getSubscriptionIds() {
+        throwIfParentInstance("getSubscriptionIds");
         if (mService != null) {
             try {
                 return intArrayToSet(mService.getSubscriptionIds(mContext.getPackageName()));
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 89199ca..7548562 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1297,7 +1297,7 @@
          */
         @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
         @Nullable
-        public GetCredentialRequest getCredentialManagerRequest() {
+        public GetCredentialRequest getPendingCredentialRequest() {
             return mGetCredentialRequest;
         }
 
@@ -1306,7 +1306,7 @@
          */
         @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
         @Nullable
-        public ResultReceiver getCredentialManagerCallback() {
+        public ResultReceiver getPendingCredentialCallback() {
             return mGetCredentialResultReceiver;
         }
 
@@ -2191,14 +2191,14 @@
 
         @Nullable
         @Override
-        public GetCredentialRequest getCredentialManagerRequest() {
+        public GetCredentialRequest getPendingCredentialRequest() {
             return mNode.mGetCredentialRequest;
         }
 
         @Nullable
         @Override
         public OutcomeReceiver<
-                GetCredentialResponse, GetCredentialException> getCredentialManagerCallback() {
+                GetCredentialResponse, GetCredentialException> getPendingCredentialCallback() {
             return mNode.mGetCredentialCallback;
         }
 
@@ -2267,7 +2267,7 @@
         }
 
         @Override
-        public void setCredentialManagerRequest(@NonNull GetCredentialRequest request,
+        public void setPendingCredentialRequest(@NonNull GetCredentialRequest request,
                 @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
             mNode.mGetCredentialRequest = request;
             mNode.mGetCredentialCallback = callback;
@@ -2654,7 +2654,7 @@
                     + ", isCredential=" + node.isCredential()
             );
         }
-        GetCredentialRequest getCredentialRequest = node.getCredentialManagerRequest();
+        GetCredentialRequest getCredentialRequest = node.getPendingCredentialRequest();
         if (getCredentialRequest == null) {
             Log.i(TAG, prefix + " No Credential Manager Request");
         } else {
diff --git a/core/java/android/app/network-policy.aconfig b/core/java/android/app/network-policy.aconfig
new file mode 100644
index 0000000..88f386f
--- /dev/null
+++ b/core/java/android/app/network-policy.aconfig
@@ -0,0 +1,11 @@
+package: "android.app"
+
+flag {
+     namespace: "backstage_power"
+     name: "clear_dns_cache_on_network_rules_update"
+     description: "Clears the DNS cache when the network rules update"
+     bug: "237556596"
+     metadata {
+       purpose: PURPOSE_BUGFIX
+     }
+}
\ No newline at end of file
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index cda4d89..2c0e035 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -822,7 +822,18 @@
      *
      * @param appWidgetIds  The AppWidget instances to notify of view data changes.
      * @param viewId        The collection view id.
+     * @deprecated The corresponding API
+     * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+     * deprecated. Moving forward please use
+     * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+     * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+     * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+     * {@link #updateAppWidget(int, RemoteViews)},
+     * {@link #updateAppWidget(ComponentName, RemoteViews)},
+     * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+     * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
      */
+    @Deprecated
     public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
         if (mService == null) {
             return;
@@ -873,7 +884,18 @@
      *
      * @param appWidgetId  The AppWidget instance to notify of view data changes.
      * @param viewId       The collection view id.
+     * @deprecated The corresponding API
+     * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+     * deprecated. Moving forward please use
+     * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+     * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+     * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+     * {@link #updateAppWidget(int, RemoteViews)},
+     * {@link #updateAppWidget(ComponentName, RemoteViews)},
+     * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+     * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
      */
+    @Deprecated
     public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) {
         if (mService == null) {
             return;
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 533fa51..55957bf 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -133,4 +133,7 @@
     void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation);
 
     List<UserHandle> getUserProfiles();
+
+    /** Saves view capture data to the wm trace directory. */
+    void saveViewCaptureData();
 }
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 41c1f17..3a5383d 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1328,6 +1328,19 @@
     }
 
     /**
+     * Saves view capture data to the default location.
+     * @hide
+     */
+    @RequiresPermission(READ_FRAME_BUFFER)
+    public void saveViewCaptureData() {
+        try {
+            mService.saveViewCaptureData();
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Unregister a callback, so that it won't be called when LauncherApps dumps.
      * @hide
      */
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index f660770..7fd0b03 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -38,7 +38,7 @@
     name: "nine_patch_frro"
     namespace: "resource_manager"
     description: "Feature flag for creating an frro from a 9-patch"
-    bug: "309232726"
+    bug: "296324826"
 }
 
 flag {
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index a3a2a2e..c5167db 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -304,7 +304,11 @@
      *
      * @param id primary identifier of a program to fetch
      * @return the program info, or null if there is no such program on the list
+     *
+     * @deprecated Use {@link #getProgramInfos(ProgramSelector.Identifier)} to get all programs
+     * with the given primary identifier
      */
+    @Deprecated
     public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) {
         Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries;
         synchronized (mLock) {
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index a968c6f..0740374 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -312,15 +312,23 @@
     public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
     /**
      * 1: AM, 2:FM
+     * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
      */
+    @Deprecated
     public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
     /**
      * 32bit primary identifier for SiriusXM Satellite Radio.
+     *
+     * @deprecated SiriusXM Satellite Radio is not supported
      */
+    @Deprecated
     public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
     /**
      * 0-999 range
+     *
+     * @deprecated SiriusXM Satellite Radio is not supported
      */
+    @Deprecated
     public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
     /**
      * 44bit compound primary identifier for Digital Audio Broadcasting and
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 61cf8901..da6c686 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -166,7 +166,12 @@
      * analog handover state managed from the HAL implementation side.
      *
      * <p>Some radio technologies may not support this, i.e. DAB.
+     *
+     * @deprecated Use {@link #CONFIG_FORCE_ANALOG_FM} instead. If {@link #CONFIG_FORCE_ANALOG_FM}
+     * is supported in HAL, {@link RadioTuner#setConfigFlag} and {@link RadioTuner#isConfigFlagSet}
+     * with CONFIG_FORCE_ANALOG will set/get the value of {@link #CONFIG_FORCE_ANALOG_FM}.
      */
+    @Deprecated
     public static final int CONFIG_FORCE_ANALOG = 2;
     /**
      * Forces the digital playback for the supporting radio technology.
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index dd8b3de..09e6b5d 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -38,6 +38,8 @@
     boolean isDreaming();
     @UnsupportedAppUsage
     boolean isDreamingOrInPreview();
+    @UnsupportedAppUsage
+    boolean canStartDreaming(boolean isScreenOn);
     void finishSelf(in IBinder token, boolean immediate);
     void startDozing(in IBinder token, int screenState, int screenBrightness);
     void stopDozing(in IBinder token);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9fab1f7..a5ff48f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7057,16 +7057,16 @@
 
     /**
      * Clears the request and callback previously set
-     * through {@link View#setCredentialManagerRequest}.
+     * through {@link View#setPendingCredentialRequest}.
      * Once this API is invoked, there will be no request fired to {@link CredentialManager}
      * on future view focus events.
      *
-     * @see #setCredentialManagerRequest
+     * @see #setPendingCredentialRequest
      */
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
-    public void clearCredentialManagerRequest() {
+    public void clearPendingCredentialRequest() {
         if (Log.isLoggable(AUTOFILL_LOG_TAG, Log.VERBOSE)) {
-            Log.v(AUTOFILL_LOG_TAG, "clearCredentialManagerRequest called");
+            Log.v(AUTOFILL_LOG_TAG, "clearPendingCredentialRequest called");
         }
         mViewCredentialHandler = null;
     }
@@ -7096,7 +7096,7 @@
      *                 propagated for the given view
      */
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
-    public void setCredentialManagerRequest(@NonNull GetCredentialRequest request,
+    public void setPendingCredentialRequest(@NonNull GetCredentialRequest request,
             @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
         Preconditions.checkNotNull(request, "request must not be null");
         Preconditions.checkNotNull(callback, "request must not be null");
@@ -9604,7 +9604,7 @@
                 structure.setIsCredential(isCredential());
             }
             if (getViewCredentialHandler() != null) {
-                structure.setCredentialManagerRequest(
+                structure.setPendingCredentialRequest(
                         getViewCredentialHandler().getRequest(),
                         getViewCredentialHandler().getCallback());
             }
@@ -10009,22 +10009,22 @@
      * @hide
      */
     public void onGetCredentialResponse(GetCredentialResponse response) {
-        if (getCredentialManagerCallback() == null) {
+        if (getPendingCredentialCallback() == null) {
             Log.w(AUTOFILL_LOG_TAG, "onGetCredentialResponse called but no callback found");
             return;
         }
-        getCredentialManagerCallback().onResult(response);
+        getPendingCredentialCallback().onResult(response);
     }
 
     /**
      * @hide
      */
     public void onGetCredentialException(String errorType, String errorMsg) {
-        if (getCredentialManagerCallback() == null) {
+        if (getPendingCredentialCallback() == null) {
             Log.w(AUTOFILL_LOG_TAG, "onGetCredentialException called but no callback found");
             return;
         }
-        getCredentialManagerCallback().onError(new GetCredentialException(errorType, errorMsg));
+        getPendingCredentialCallback().onError(new GetCredentialException(errorType, errorMsg));
     }
 
     /**
@@ -10055,13 +10055,13 @@
      * the active {@link android.service.autofill.AutofillService} on
      * the device.
      *
-     * <p>See {@link #setCredentialManagerRequest} for more info.
+     * <p>See {@link #setPendingCredentialRequest} for more info.
      *
      * @return The credential request associated with this View.
      */
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
     @Nullable
-    public final GetCredentialRequest getCredentialManagerRequest() {
+    public final GetCredentialRequest getPendingCredentialRequest() {
         if (mViewCredentialHandler == null) {
             return null;
         }
@@ -10071,14 +10071,14 @@
 
     /**
      * Returns the callback that has previously been set up on this view through
-     * the {@link #setCredentialManagerRequest} API.
+     * the {@link #setPendingCredentialRequest} API.
      * If the return value is null, that means no callback, or request, has been set
      * on the view and no {@link CredentialManager} flow will be invoked
      * when this view is focused. Traditioanl autofill flows will still
      * work, and autofillable content will still be returned through the
      * {@link #autofill(AutofillValue)} )} API.
      *
-     * <p>See {@link #setCredentialManagerRequest} for more info.
+     * <p>See {@link #setPendingCredentialRequest} for more info.
      *
      * @return The callback associated with this view that will be invoked on a response from
      * {@link CredentialManager} .
@@ -10086,7 +10086,7 @@
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
     @Nullable
     public final OutcomeReceiver<GetCredentialResponse,
-            GetCredentialException> getCredentialManagerCallback() {
+            GetCredentialException> getPendingCredentialCallback() {
         if (mViewCredentialHandler == null) {
             return null;
         }
@@ -11042,7 +11042,7 @@
                     AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId())));
         }
         if (getViewCredentialHandler() != null) {
-            structure.setCredentialManagerRequest(
+            structure.setPendingCredentialRequest(
                     getViewCredentialHandler().getRequest(),
                     getViewCredentialHandler().getCallback());
         }
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 1efd375..6c852c3 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -361,11 +361,11 @@
      * {@link android.credentials.CredentialManager} request will be fired when this
      * node is focused.
      * <p> For details on how a request and callback can be set, see
-     * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)}
+     * {@link ViewStructure#setPendingCredentialRequest(GetCredentialRequest, OutcomeReceiver)}
      */
     @Nullable
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
-    public GetCredentialRequest getCredentialManagerRequest() {
+    public GetCredentialRequest getPendingCredentialRequest() {
         return null;
     }
 
@@ -376,12 +376,12 @@
      * {@link android.credentials.CredentialManager} request will be fired when this
      * node is focused.
      * <p> For details on how a request and callback can be set, see
-     * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)}
+     * {@link ViewStructure#setPendingCredentialRequest(GetCredentialRequest, OutcomeReceiver)}
      */
     @Nullable
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
     public OutcomeReceiver<
-            GetCredentialResponse, GetCredentialException> getCredentialManagerCallback() {
+            GetCredentialResponse, GetCredentialException> getPendingCredentialCallback() {
         return null;
     }
 
@@ -555,12 +555,12 @@
      * @param callback the callback where the response or exception, is returned
      */
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
-    public void setCredentialManagerRequest(@NonNull GetCredentialRequest request,
+    public void setPendingCredentialRequest(@NonNull GetCredentialRequest request,
             @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {}
 
     /**
      * Clears the credential request previously set through
-     * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)}
+     * {@link ViewStructure#setPendingCredentialRequest(GetCredentialRequest, OutcomeReceiver)}
      */
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
     public void clearCredentialManagerRequest() {}
diff --git a/core/java/com/android/internal/compat/Android.bp b/core/java/com/android/internal/compat/Android.bp
new file mode 100644
index 0000000..9ff05a6
--- /dev/null
+++ b/core/java/com/android/internal/compat/Android.bp
@@ -0,0 +1,7 @@
+aconfig_declarations {
+    name: "compat_logging_flags",
+    package: "com.android.internal.compat.flags",
+    srcs: [
+        "compat_logging_flags.aconfig",
+    ],
+}
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
index b9d3df6..6ff546f 100644
--- a/core/java/com/android/internal/compat/ChangeReporter.java
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -24,6 +24,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.flags.Flags;
 import com.android.internal.util.FrameworkStatsLog;
 
 import java.lang.annotation.Retention;
@@ -40,7 +41,7 @@
  * @hide
  */
 public final class ChangeReporter {
-    private static final String TAG = "CompatibilityChangeReporter";
+    private static final String TAG = "CompatChangeReporter";
     private int mSource;
 
     private static final class ChangeReport {
@@ -84,19 +85,34 @@
      * Report the change to stats log and to the debug log if the change was not previously
      * logged already.
      *
+     * @param uid             affected by the change
+     * @param changeId        the reported change id
+     * @param state           of the reported change - enabled/disabled/only logged
+     * @param isLoggableBySdk whether debug logging is allowed for this change based on target
+     *                        SDK version. This is combined with other logic to determine whether to
+     *                        actually log. If the sdk version does not matter, should be true.
+     */
+    public void reportChange(int uid, long changeId, int state, boolean isLoggableBySdk) {
+        if (shouldWriteToStatsLog(uid, changeId, state)) {
+            FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid,
+                    changeId, state, mSource);
+        }
+        if (shouldWriteToDebug(uid, changeId, state, isLoggableBySdk)) {
+            debugLog(uid, changeId, state);
+        }
+        markAsReported(uid, new ChangeReport(changeId, state));
+    }
+
+    /**
+     * Report the change to stats log and to the debug log if the change was not previously
+     * logged already.
+     *
      * @param uid      affected by the change
      * @param changeId the reported change id
      * @param state    of the reported change - enabled/disabled/only logged
      */
     public void reportChange(int uid, long changeId, int state) {
-        if (shouldWriteToStatsLog(uid, changeId, state)) {
-            FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid,
-                    changeId, state, mSource);
-        }
-        if (shouldWriteToDebug(uid, changeId, state)) {
-            debugLog(uid, changeId, state);
-        }
-        markAsReported(uid, new ChangeReport(changeId, state));
+        reportChange(uid, changeId, state, true);
     }
 
     /**
@@ -130,14 +146,43 @@
     /**
      * Returns whether the next report should be logged to logcat.
      *
-     * @param uid      affected by the change
-     * @param changeId the reported change id
-     * @param state    of the reported change - enabled/disabled/only logged
+     * @param uid             affected by the change
+     * @param changeId        the reported change id
+     * @param state           of the reported change - enabled/disabled/only logged
+     * @param isLoggableBySdk whether debug logging is allowed for this change based on target
+     *                        SDK version. This is combined with other logic to determine whether to
+     *                        actually log. If the sdk version does not matter, should be true.
+     * @return true if the report should be logged
+     */
+    @VisibleForTesting
+    public boolean shouldWriteToDebug(
+            int uid, long changeId, int state, boolean isLoggableBySdk) {
+        // If log all bit is on, always return true.
+        if (mDebugLogAll) return true;
+        // If the change has already been reported, do not write.
+        if (isAlreadyReported(uid, new ChangeReport(changeId, state))) return false;
+
+        // If the flag is turned off or the TAG's logging is forced to debug level with
+        // `adb setprop log.tag.CompatChangeReporter=DEBUG`, write to debug since the above checks
+        // have already passed.
+        boolean skipLoggingFlag = Flags.skipOldAndDisabledCompatLogging();
+        if (!skipLoggingFlag || Log.isLoggable(TAG, Log.DEBUG)) return true;
+
+        // Log if the change is enabled and targets the latest sdk version.
+        return isLoggableBySdk && state != STATE_DISABLED;
+    }
+
+    /**
+     * Returns whether the next report should be logged to logcat.
+     *
+     * @param uid         affected by the change
+     * @param changeId    the reported change id
+     * @param state       of the reported change - enabled/disabled/only logged
      * @return true if the report should be logged
      */
     @VisibleForTesting
     public boolean shouldWriteToDebug(int uid, long changeId, int state) {
-        return mDebugLogAll || !isAlreadyReported(uid, new ChangeReport(changeId, state));
+        return shouldWriteToDebug(uid, changeId, state, true);
     }
 
     private boolean isAlreadyReported(int uid, ChangeReport report) {
diff --git a/core/java/com/android/internal/compat/compat_logging_flags.aconfig b/core/java/com/android/internal/compat/compat_logging_flags.aconfig
new file mode 100644
index 0000000..fab3856
--- /dev/null
+++ b/core/java/com/android/internal/compat/compat_logging_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.internal.compat.flags"
+
+flag {
+    name: "skip_old_and_disabled_compat_logging"
+    namespace: "platform_compat"
+    description: "Feature flag for skipping debug logging for changes that do not target the latest sdk or are disabled"
+    bug: "323949942"
+    is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index dc3b5a8..0257033 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -33,8 +33,10 @@
 import com.android.internal.inputmethod.InputBindResult;
 
 /**
- * Public interface to the global input method manager, used by all client
- * applications.
+ * Public interface to the global input method manager, used by all client applications.
+ *
+ * When adding new methods, make sure the associated user can be inferred from the arguments.
+ * Consider passing the associated userId when not already passing a display id or a window token.
  */
 interface IInputMethodManager {
     void addClient(in IInputMethodClient client, in IRemoteInputConnection inputmethod,
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
index a034653..10ac05d 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
@@ -88,13 +88,6 @@
     }
 
     @Test
-    public void setInternalHalCallback_callbackSetInHal() throws Exception {
-        mRadioModule.setInternalHalCallback();
-
-        verify(mBroadcastRadioMock).setTunerCallback(any());
-    }
-
-    @Test
     public void getImage_withValidIdFromRadioModule() {
         int imageId = 1;
 
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 262f167..755bcdb 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -192,66 +192,6 @@
             mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
             return null;
         }).when(mBroadcastRadioMock).setTunerCallback(any());
-        mRadioModule.setInternalHalCallback();
-
-        doAnswer(invocation -> {
-            android.hardware.broadcastradio.ProgramSelector halSel =
-                    (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0];
-            mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
-            if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ) {
-                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
-            }
-            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
-            return Result.OK;
-        }).when(mBroadcastRadioMock).tune(any());
-
-        doAnswer(invocation -> {
-            if ((boolean) invocation.getArguments()[0]) {
-                mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
-            } else {
-                mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
-            }
-            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
-            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
-            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
-            return Result.OK;
-        }).when(mBroadcastRadioMock).step(anyBoolean());
-
-        doAnswer(invocation -> {
-            if (mHalCurrentInfo == null) {
-                android.hardware.broadcastradio.ProgramSelector placeHolderSelector =
-                        AidlTestUtils.makeHalFmSelector(/* freq= */ 97300);
-
-                mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
-                return Result.OK;
-            }
-            mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
-                    mHalCurrentInfo.selector.primaryId.value,
-                    !(boolean) invocation.getArguments()[0]);
-            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
-            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
-            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
-            return Result.OK;
-        }).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
-
-        doReturn(null).when(mBroadcastRadioMock).getImage(anyInt());
-
-        doAnswer(invocation -> {
-            int configFlag = (int) invocation.getArguments()[0];
-            if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
-                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
-            }
-            return mHalConfigMap.getOrDefault(configFlag, false);
-        }).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
-
-        doAnswer(invocation -> {
-            int configFlag = (int) invocation.getArguments()[0];
-            if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
-                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
-            }
-            mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]);
-            return null;
-        }).when(mBroadcastRadioMock).setConfigFlag(anyInt(), anyBoolean());
     }
 
     @After
@@ -330,6 +270,7 @@
 
         expect.withMessage("Close state of broadcast radio service session")
                 .that(mTunerSessions[0].isClosed()).isTrue();
+        verify(mBroadcastRadioMock).unsetTunerCallback();
     }
 
     @Test
@@ -351,6 +292,7 @@
                         .that(mTunerSessions[index].isClosed()).isFalse();
             }
         }
+        verify(mBroadcastRadioMock, never()).unsetTunerCallback();
     }
 
     @Test
@@ -378,6 +320,7 @@
             expect.withMessage("Close state of broadcast radio service session of index %s", index)
                     .that(mTunerSessions[index].isClosed()).isTrue();
         }
+        verify(mBroadcastRadioMock).unsetTunerCallback();
     }
 
     @Test
@@ -1295,6 +1238,71 @@
             mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
             mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
         }
+        setupMockedHalTunerSession();
+    }
+
+    private void setupMockedHalTunerSession() throws Exception {
+        expect.withMessage("Registered HAL tuner callback").that(mHalTunerCallback)
+                .isNotNull();
+
+        doAnswer(invocation -> {
+            android.hardware.broadcastradio.ProgramSelector halSel =
+                    (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0];
+            mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
+            if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ) {
+                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+            }
+            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+            return Result.OK;
+        }).when(mBroadcastRadioMock).tune(any());
+
+        doAnswer(invocation -> {
+            if ((boolean) invocation.getArguments()[0]) {
+                mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
+            } else {
+                mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
+            }
+            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+            return Result.OK;
+        }).when(mBroadcastRadioMock).step(anyBoolean());
+
+        doAnswer(invocation -> {
+            if (mHalCurrentInfo == null) {
+                android.hardware.broadcastradio.ProgramSelector placeHolderSelector =
+                        AidlTestUtils.makeHalFmSelector(/* freq= */ 97300);
+
+                mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
+                return Result.OK;
+            }
+            mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
+                    mHalCurrentInfo.selector.primaryId.value,
+                    !(boolean) invocation.getArguments()[0]);
+            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+            return Result.OK;
+        }).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
+
+        doReturn(null).when(mBroadcastRadioMock).getImage(anyInt());
+
+        doAnswer(invocation -> {
+            int configFlag = (int) invocation.getArguments()[0];
+            if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
+                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+            }
+            return mHalConfigMap.getOrDefault(configFlag, false);
+        }).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
+
+        doAnswer(invocation -> {
+            int configFlag = (int) invocation.getArguments()[0];
+            if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
+                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+            }
+            mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]);
+            return null;
+        }).when(mBroadcastRadioMock).setConfigFlag(anyInt(), anyBoolean());
     }
 
     private long getSeekFrequency(long currentFrequency, boolean seekDown) {
diff --git a/core/tests/PlatformCompatFramework/Android.bp b/core/tests/PlatformCompatFramework/Android.bp
index 95e23ad..2621d28 100644
--- a/core/tests/PlatformCompatFramework/Android.bp
+++ b/core/tests/PlatformCompatFramework/Android.bp
@@ -18,6 +18,7 @@
     static_libs: [
         "junit",
         "androidx.test.rules",
+        "flag-junit",
     ],
     platform_apis: true,
 }
diff --git a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
index a052543..12a42f9 100644
--- a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
+++ b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
@@ -19,9 +19,17 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.internal.compat.flags.Flags;
+
+import org.junit.Rule;
 import org.junit.Test;
 
 public class ChangeReporterTest {
+
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Test
     public void testStatsLogOnce() {
         ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
@@ -63,7 +71,7 @@
         ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
         int myUid = 1022, otherUid = 1023;
         long myChangeId = 500L, otherChangeId = 600L;
-        int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_DISABLED;
+        int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_LOGGED;
 
         assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
         reporter.reportChange(myUid, myChangeId, myState);
@@ -112,4 +120,80 @@
         reporter.stopDebugLogAll();
         assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
     }
+
+    @Test
+    public void testDebugLogWithFlagOnAndOldSdk() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING);
+        ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+        int myUid = 1022;
+        long myChangeId = 500L;
+        int myEnabledState = ChangeReporter.STATE_ENABLED;
+        int myDisabledState = ChangeReporter.STATE_DISABLED;
+
+        // Report will not log if target sdk is before the previous version.
+        assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false));
+
+        reporter.resetReportedChanges(myUid);
+
+        // Report will be logged if target sdk is the latest version.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true));
+
+        reporter.resetReportedChanges(myUid);
+
+        // If the report is disabled, the sdk version shouldn't matter.
+        assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true));
+    }
+
+    @Test
+    public void testDebugLogWithFlagOnAndDisabledChange() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING);
+        ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+        int myUid = 1022;
+        long myChangeId = 500L;
+        int myEnabledState = ChangeReporter.STATE_ENABLED;
+        int myDisabledState = ChangeReporter.STATE_DISABLED;
+
+        // Report will not log if the change is disabled.
+        assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true));
+
+        reporter.resetReportedChanges(myUid);
+
+        // Report will be logged if the change is enabled.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true));
+
+        reporter.resetReportedChanges(myUid);
+
+        // If the report is not the latest version, the disabled state doesn't matter.
+        assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false));
+    }
+
+    @Test
+    public void testDebugLogWithFlagOff() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING);
+        ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+        int myUid = 1022;
+        long myChangeId = 500L;
+        int myEnabledState = ChangeReporter.STATE_ENABLED;
+        int myDisabledState = ChangeReporter.STATE_DISABLED;
+
+        // Report will be logged even if the change is not the latest sdk but the flag is off.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false));
+
+        reporter.resetReportedChanges(myUid);
+
+        // Report will be logged if the change is enabled and the latest sdk but the flag is off.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true));
+
+        reporter.resetReportedChanges(myUid);
+
+        // Report will be logged if the change is disabled and the latest sdk but the flag is
+        // off.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true));
+
+        reporter.resetReportedChanges(myUid);
+
+        // Report will be logged if the change is disabled and not the latest sdk but the flag is
+        // off.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, false));
+    }
 }
diff --git a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
index abeb08c..1f2788c 100644
--- a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
+++ b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
@@ -266,8 +266,8 @@
         assertThat(view.getViewRootImpl()).isNotNull();
         ViewNodeBuilder viewStructure = new ViewNodeBuilder();
         viewStructure.setAutofillId(view.getAutofillId());
-        viewStructure.setCredentialManagerRequest(view.getCredentialManagerRequest(),
-                view.getCredentialManagerCallback());
+        viewStructure.setPendingCredentialRequest(view.getPendingCredentialRequest(),
+                view.getPendingCredentialCallback());
         view.onProvideAutofillStructure(viewStructure, /* flags= */ 0);
         ViewNodeParcelable viewNodeParcelable = new ViewNodeParcelable(viewStructure.getViewNode());
 
@@ -289,17 +289,20 @@
 
         assertThat(view.getViewRootImpl()).isNotNull();
         ViewNodeBuilder viewStructure = new ViewNodeBuilder();
-        viewStructure.setCredentialManagerRequest(view.getCredentialManagerRequest(),
-                view.getCredentialManagerCallback());
+        if (view.getPendingCredentialRequest() != null
+                && view.getPendingCredentialCallback() != null) {
+            viewStructure.setPendingCredentialRequest(view.getPendingCredentialRequest(),
+                    view.getPendingCredentialCallback());
+        }
 
-        assertEquals(viewStructure.getCredentialManagerRequest(), GET_CREDENTIAL_REQUEST);
-        assertEquals(viewStructure.getCredentialManagerCallback(),
+        assertEquals(viewStructure.getPendingCredentialRequest(), GET_CREDENTIAL_REQUEST);
+        assertEquals(viewStructure.getPendingCredentialCallback(),
                 GET_CREDENTIAL_REQUEST_CALLBACK);
 
         viewStructure.clearCredentialManagerRequest();
 
-        assertNull(viewStructure.getCredentialManagerRequest());
-        assertNull(viewStructure.getCredentialManagerCallback());
+        assertNull(viewStructure.getPendingCredentialRequest());
+        assertNull(viewStructure.getPendingCredentialCallback());
     }
 
     @Test
@@ -386,14 +389,14 @@
         EditText view = new EditText(mContext);
         view.setText("Big Hint in Little View");
         view.setAutofillHints(BIG_STRING);
-        view.setCredentialManagerRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
+        view.setPendingCredentialRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
         return view;
     }
 
     private EditText newCredentialView() {
         EditText view = new EditText(mContext);
         view.setText("Credential Request");
-        view.setCredentialManagerRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
+        view.setPendingCredentialRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
         return view;
     }
 
@@ -421,8 +424,8 @@
         assertThat(view.getAutofillId()).isNotNull();
         assertThat(view.getText().toString()).isEqualTo("Big Hint in Little View");
 
-        assertThat(view.getCredentialManagerRequest()).isEqualTo(GET_CREDENTIAL_REQUEST);
-        assertThat(view.getCredentialManagerCallback()).isEqualTo(GET_CREDENTIAL_REQUEST_CALLBACK);
+        assertThat(view.getPendingCredentialRequest()).isEqualTo(GET_CREDENTIAL_REQUEST);
+        assertThat(view.getPendingCredentialCallback()).isEqualTo(GET_CREDENTIAL_REQUEST_CALLBACK);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 474430e..23bdd08 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -2474,11 +2474,12 @@
         // Let the expanded animation controller know that it shouldn't animate child adds/reorders
         // since we're about to animate collapsed.
         mExpandedAnimationController.notifyPreparingToCollapse();
-
+        final PointF collapsePosition = mStackAnimationController
+                .getStackPositionAlongNearestHorizontalEdge();
         updateOverflowDotVisibility(false /* expanding */);
         final Runnable collapseBackToStack = () ->
                 mExpandedAnimationController.collapseBackToStack(
-                        mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(),
+                        collapsePosition,
                         /* fadeBubblesDuringCollapse= */ mRemovingLastBubbleWhileExpanded,
                         () -> {
                             mBubbleContainer.setActiveController(mStackAnimationController);
@@ -2501,7 +2502,8 @@
             }
             mExpandedViewAnimationController.reset();
         };
-        mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after);
+        mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after,
+                collapsePosition);
         if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
             // When the animation completes, we should no longer be showing the content.
             // This won't actually update content visibility immediately, if we are currently
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
index 8a33780..4175529 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
@@ -15,6 +15,8 @@
  */
 package com.android.wm.shell.bubbles.animation;
 
+import android.graphics.PointF;
+
 import com.android.wm.shell.bubbles.BubbleExpandedView;
 
 /**
@@ -55,8 +57,9 @@
      * @param startStackCollapse runnable that is triggered when bubbles can start moving back to
      *                           their collapsed location
      * @param after              runnable to run after animation is complete
+     * @param collapsePosition the position on screen the stack will collapse to
      */
-    void animateCollapse(Runnable startStackCollapse, Runnable after);
+    void animateCollapse(Runnable startStackCollapse, Runnable after, PointF collapsePosition);
 
     /**
      * Animate the view back to fully expanded state.
@@ -69,6 +72,22 @@
     void animateForImeVisibilityChange(boolean visible);
 
     /**
+     * Whether this controller should also animate the expansion for the bubble
+     */
+    boolean shouldAnimateExpansion();
+
+    /**
+     * Animate the expansion of the bubble.
+     *
+     * @param startDelayMillis how long to delay starting the expansion animation
+     * @param after runnable to run after the animation is complete
+     * @param collapsePosition the position on screen the stack will collapse to (and expand from)
+     * @param bubblePosition the position of the bubble on screen that the view is associated with
+     */
+    void animateExpansion(long startDelayMillis, Runnable after, PointF collapsePosition,
+            PointF bubblePosition);
+
+    /**
      * Reset the view to fully expanded state
      */
     void reset();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
index e43609f..aa4129a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
@@ -28,6 +28,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
 import android.content.Context;
+import android.graphics.PointF;
 import android.view.HapticFeedbackConstants;
 import android.view.ViewConfiguration;
 
@@ -187,9 +188,11 @@
     }
 
     @Override
-    public void animateCollapse(Runnable startStackCollapse, Runnable after) {
-        ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d",
-                mSwipeUpVelocity,  mMinFlingVelocity);
+    public void animateCollapse(Runnable startStackCollapse, Runnable after,
+            PointF collapsePosition) {
+        ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d"
+                        + " collapsePosition=%f,%f", mSwipeUpVelocity, mMinFlingVelocity,
+                collapsePosition.x, collapsePosition.y);
         if (mExpandedView != null) {
             // Mark it as animating immediately to avoid updates to the view before animation starts
             mExpandedView.setAnimating(true);
@@ -274,6 +277,17 @@
     }
 
     @Override
+    public boolean shouldAnimateExpansion() {
+        return false;
+    }
+
+    @Override
+    public void animateExpansion(long startDelayMillis, Runnable after, PointF collapsePosition,
+            PointF bubblePosition) {
+        // TODO - animate
+    }
+
+    @Override
     public void reset() {
         ProtoLog.d(WM_SHELL_BUBBLES, "reset expandedView collapsed state");
         if (mExpandedView == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index f8b5530..235456c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -544,7 +544,8 @@
                     apps.add(target);
                 } else {
                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                            "  unhandled change taskId=%d", taskInfo.taskId);
+                            "  unhandled change taskId=%d",
+                            taskInfo != null ? taskInfo.taskId : -1);
                 }
             }
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 9c01442..98ff0ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -866,6 +866,12 @@
                     return;
                 }
                 if (mTransitionDragActive) {
+                    // Do not create an indicator at all if we're not past transition height.
+                    if (ev.getRawY() < mContext.getResources().getDimensionPixelSize(com.android
+                            .wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height)
+                            && mMoveToDesktopAnimator == null) {
+                        return;
+                    }
                     final DesktopModeVisualIndicator.IndicatorType indicatorType =
                             mDesktopTasksController.updateVisualIndicator(
                                     relevantDecor.mTaskInfo,
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 6cf9c6f..bf39425 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -109,5 +109,5 @@
     name: "enable_null_session_in_media_browser_service"
     namespace: "media_solutions"
     description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
-    bug: "263520343"
+    bug: "185136506"
 }
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 76664a6..ddcb25b 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -61,7 +61,6 @@
 @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
 @SystemService(Context.TV_AD_SERVICE)
 public final class TvAdManager {
-    // TODO: implement more methods and unhide APIs.
     private static final String TAG = "TvAdManager";
 
     /**
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index d204908..dd2a534 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -393,16 +393,12 @@
     }
 
     /**
-     * Sets a listener to be invoked when an input event is not handled
-     * by the TV AD service.
+     * Sets a listener to be invoked when an input event is not handled by the TV AD service.
      *
      * @param listener The callback to be invoked when the unhandled input event is received.
      */
-    public void setOnUnhandledInputEventListener(
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull OnUnhandledInputEventListener listener) {
+    public void setOnUnhandledInputEventListener(@NonNull OnUnhandledInputEventListener listener) {
         mOnUnhandledInputEventListener = listener;
-        // TODO: handle CallbackExecutor
     }
 
     /**
@@ -441,6 +437,9 @@
     /**
      * Prepares the AD service of corresponding {@link TvAdService}.
      *
+     * <p>This should be called before calling {@link #startAdService()}. Otherwise,
+     * {@link #startAdService()} is a no-op.
+     *
      * @param serviceId the AD service ID, which can be found in TvAdServiceInfo#getId().
      */
     public void prepareAdService(@NonNull String serviceId, @NonNull String type) {
@@ -455,6 +454,9 @@
 
     /**
      * Starts the AD service.
+     *
+     * <p>This should be called after calling {@link #prepareAdService(String, String)}. Otherwise,
+     * it's a no-op.
      */
     public void startAdService() {
         if (DEBUG) {
@@ -467,6 +469,8 @@
 
     /**
      * Stops the AD service.
+     *
+     * <p>It's a no-op if the service is not started.
      */
     public void stopAdService() {
         if (DEBUG) {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
index 4500a22..5dcfd3b 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
@@ -40,7 +40,7 @@
      *
      * This can be called using the JavaScript below:
      * <script type="text/javascript">
-     *     function getRequestedCapability(duration) {
+     *     function getRequestedCapability() {
      *         DataBoostWebServiceFlow.getRequestedCapability();
      *     }
      * </script>
@@ -57,6 +57,25 @@
      *
      * This can be called using the JavaScript below:
      * <script type="text/javascript">
+     *     function notifyPurchaseSuccessful(duration_ms_long = 0) {
+     *         DataBoostWebServiceFlow.notifyPurchaseSuccessful(duration_ms_long);
+     *     }
+     * </script>
+     *
+     * @param duration The duration for which the premium capability is purchased in milliseconds.
+     *                 NOTE: The duration parameter is not used.
+     */
+    @JavascriptInterface
+    public void notifyPurchaseSuccessful(long duration) {
+        mActivity.onPurchaseSuccessful();
+    }
+
+    /**
+     * Interface method allowing the carrier website to notify the slice purchase application of
+     * a successful premium capability purchase.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
      *     function notifyPurchaseSuccessful() {
      *         DataBoostWebServiceFlow.notifyPurchaseSuccessful();
      *     }
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 6019aa8..42d0cc4 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -52,12 +52,21 @@
     <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
     <string name="title_app_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone</string>
 
+    <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) with mirroring enabled [CHAR LIMIT=NONE] -->
+    <string name="title_app_streaming_with_mirroring">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to stream your phone\u2019s apps?</string>
+
+    <!-- Summary for associating an application with a companion device of APP_STREAMING profile [CHAR LIMIT=NONE] -->
+    <string name="summary_app_streaming">%1$s will have access to anything that’s visible or played on the phone, including audio, photos, passwords, and messages.&lt;br/>&lt;br/>%1$s will be able to stream apps until you remove access to this permission.</string>
+
     <!-- Title of the helper dialog for APP_STREAMING profile [CHAR LIMIT=30]. -->
     <string name="helper_title_app_streaming">Cross-device services</string>
 
     <!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] -->
     <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to stream apps between your devices</string>
 
+    <!-- Description of the helper dialog for APP_STREAMING profile with mirroring enabled. [CHAR LIMIT=NONE] -->
+    <string name="helper_summary_app_streaming_with_mirroring"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to display and stream apps between your devices</string>
+
     <!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
 
     <!-- Confirmation for associating an application with a companion device of AUTOMOTIVE_PROJECTION profile (type) [CHAR LIMIT=NONE] -->
@@ -85,6 +94,12 @@
     <!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) [CHAR LIMIT=NONE] -->
     <string name="title_nearby_device_streaming">Allow &lt;strong&gt;<xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g>&lt;/strong&gt; to take this action?</string>
 
+    <!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) with mirroring enabled [CHAR LIMIT=NONE] -->
+    <string name="title_nearby_device_streaming_with_mirroring">Allow &lt;strong&gt;<xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g>&lt;/strong&gt; to stream your phone\u2019s apps and system features?</string>
+
+    <!-- Summary for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile [CHAR LIMIT=NONE] -->
+    <string name="summary_nearby_device_streaming">%1$s will have access to anything that’s visible or played on your phone, including audio, photos, payment info, passwords, and messages.&lt;br/>&lt;br/>%1$s will be able to stream apps and system features until you remove access to this permission.</string>
+
     <!-- Description of the helper dialog for NEARBY_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] -->
     <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="NearbyDevice">%2$s</xliff:g> to stream apps and other system features to nearby devices</string>
 
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 4c1f631..1231b63 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -179,7 +179,7 @@
     // onActivityResult() after the association is created.
     private @Nullable DeviceFilterPair<?> mSelectedDevice;
 
-    private LinearLayoutManager mPermissionsLayoutManager = new LinearLayoutManager(this);
+    private final LinearLayoutManager mPermissionsLayoutManager = new LinearLayoutManager(this);
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -484,10 +484,18 @@
         }
 
         title = getHtmlFromResources(this, PROFILE_TITLES.get(deviceProfile), deviceName);
+
+        if (PROFILE_SUMMARIES.containsKey(deviceProfile)) {
+            final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile);
+            final Spanned summary = getHtmlFromResources(this, summaryResourceId,
+                    deviceName);
+            mSummary.setText(summary);
+        } else {
+            mSummary.setVisibility(View.GONE);
+        }
+
         setupPermissionList(deviceProfile);
 
-        // Summary is not needed for selfManaged dialog.
-        mSummary.setVisibility(View.GONE);
         mTitle.setText(title);
         mVendorHeaderName.setText(vendorName);
         mVendorHeader.setVisibility(View.VISIBLE);
@@ -692,6 +700,11 @@
     private void setupPermissionList(String deviceProfile) {
         final List<Integer> permissionTypes = new ArrayList<>(
                 PROFILE_PERMISSIONS.get(deviceProfile));
+        if (permissionTypes.isEmpty()) {
+            // Nothing to do if there are no permission types.
+            return;
+        }
+
         mPermissionListAdapter.setPermissionType(permissionTypes);
         mPermissionListRecyclerView.setAdapter(mPermissionListAdapter);
         mPermissionListRecyclerView.setLayoutManager(mPermissionsLayoutManager);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index 23a11d6..dc68bcc 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -27,11 +27,13 @@
 import static java.util.Collections.unmodifiableMap;
 import static java.util.Collections.unmodifiableSet;
 
+import android.companion.virtual.flags.Flags;
 import android.os.Build;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -122,10 +124,19 @@
     static final Map<String, Integer> PROFILE_TITLES;
     static {
         final Map<String, Integer> map = new ArrayMap<>();
-        map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming);
+        if (Flags.interactiveScreenMirror()) {
+            map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming_with_mirroring);
+        } else {
+            map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming);
+        }
         map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, R.string.title_automotive_projection);
         map.put(DEVICE_PROFILE_COMPUTER, R.string.title_computer);
-        map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming);
+        if (Flags.interactiveScreenMirror()) {
+            map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+                    R.string.title_nearby_device_streaming_with_mirroring);
+        } else {
+            map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming);
+        }
         map.put(DEVICE_PROFILE_WATCH, R.string.confirmation_title);
         map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title_glasses);
         map.put(null, R.string.confirmation_title);
@@ -138,6 +149,11 @@
         final Map<String, Integer> map = new ArrayMap<>();
         map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch);
         map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses);
+        if (Flags.interactiveScreenMirror()) {
+            map.put(DEVICE_PROFILE_APP_STREAMING, R.string.summary_app_streaming);
+            map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+                    R.string.summary_nearby_device_streaming);
+        }
         map.put(null, R.string.summary_generic);
 
         PROFILE_SUMMARIES = unmodifiableMap(map);
@@ -146,11 +162,16 @@
     static final Map<String, List<Integer>> PROFILE_PERMISSIONS;
     static {
         final Map<String, List<Integer>> map = new ArrayMap<>();
-        map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING));
         map.put(DEVICE_PROFILE_COMPUTER, Arrays.asList(
                 PERMISSION_NOTIFICATION_LISTENER_ACCESS, PERMISSION_STORAGE));
-        map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
-                Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING));
+        if (Flags.interactiveScreenMirror()) {
+            map.put(DEVICE_PROFILE_APP_STREAMING, Collections.emptyList());
+            map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, Collections.emptyList());
+        } else {
+            map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING));
+            map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+                    Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING));
+        }
         if (Build.VERSION.SDK_INT > UPSIDE_DOWN_CAKE) {
             map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATIONS, PERMISSION_PHONE,
                     PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
index 8f32dbb..fe0e021 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
@@ -26,6 +26,7 @@
 
 import android.annotation.Nullable;
 import android.companion.AssociationRequest;
+import android.companion.virtual.flags.Flags;
 import android.content.DialogInterface;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
@@ -129,7 +130,9 @@
             case DEVICE_PROFILE_APP_STREAMING:
                 title = getHtmlFromResources(getContext(), R.string.helper_title_app_streaming);
                 summary = getHtmlFromResources(
-                        getContext(), R.string.helper_summary_app_streaming, title, displayName);
+                        getContext(), Flags.interactiveScreenMirror()
+                                ? R.string.helper_summary_app_streaming_with_mirroring
+                                : R.string.helper_summary_app_streaming, title, displayName);
                 break;
 
             case DEVICE_PROFILE_COMPUTER:
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
index 5d71b7d..37b5d40 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
@@ -38,15 +38,15 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
-import android.util.BackgroundThread;
-import android.util.LongArrayQueue;
 import android.util.Slog;
 import android.util.Xml;
+import android.utils.BackgroundThread;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index 9217e70..0fcec26 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -29,7 +29,6 @@
 import android.content.pm.VersionedPackage;
 import android.os.Build;
 import android.os.Environment;
-import android.os.FileUtils;
 import android.os.PowerManager;
 import android.os.RecoverySystem;
 import android.os.SystemClock;
@@ -42,10 +41,11 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
+import android.utils.ArrayUtils;
+import android.utils.FileUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.server.PackageWatchdog.FailureReasons;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
new file mode 100644
index 0000000..fa4d6af
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.File;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
+ *
+ * @hide
+ */
+public class ArrayUtils {
+    private ArrayUtils() { /* cannot be instantiated */ }
+    public static final File[] EMPTY_FILE = new File[0];
+
+
+    /**
+     * Return first index of {@code value} in {@code array}, or {@code -1} if
+     * not found.
+     */
+    public static <T> int indexOf(@Nullable T[] array, T value) {
+        if (array == null) return -1;
+        for (int i = 0; i < array.length; i++) {
+            if (Objects.equals(array[i], value)) return i;
+        }
+        return -1;
+    }
+
+    /** @hide */
+    public static @NonNull File[] defeatNullable(@Nullable File[] val) {
+        return (val != null) ? val : EMPTY_FILE;
+    }
+
+    /**
+     * Checks if given array is null or has zero elements.
+     */
+    public static boolean isEmpty(@Nullable int[] array) {
+        return array == null || array.length == 0;
+    }
+
+    /**
+     * True if the byte array is null or has length 0.
+     */
+    public static boolean isEmpty(@Nullable byte[] array) {
+        return array == null || array.length == 0;
+    }
+
+    /**
+     * Converts from List of bytes to byte array
+     * @param list
+     * @return byte[]
+     */
+    public static byte[] toPrimitive(List<byte[]> list) {
+        if (list.size() == 0) {
+            return new byte[0];
+        }
+        int byteLen = list.get(0).length;
+        byte[] array = new byte[list.size() * byteLen];
+        for (int i = 0; i < list.size(); i++) {
+            for (int j = 0; j < list.get(i).length; j++) {
+                array[i * byteLen + j] = list.get(i)[j];
+            }
+        }
+        return array;
+    }
+
+    /**
+     * Adds value to given array if not already present, providing set-like
+     * behavior.
+     */
+    public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
+        return appendInt(cur, val, false);
+    }
+
+    /**
+     * Adds value to given array.
+     */
+    public static @NonNull int[] appendInt(@Nullable int[] cur, int val,
+            boolean allowDuplicates) {
+        if (cur == null) {
+            return new int[] { val };
+        }
+        final int n = cur.length;
+        if (!allowDuplicates) {
+            for (int i = 0; i < n; i++) {
+                if (cur[i] == val) {
+                    return cur;
+                }
+            }
+        }
+        int[] ret = new int[n + 1];
+        System.arraycopy(cur, 0, ret, 0, n);
+        ret[n] = val;
+        return ret;
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
similarity index 99%
rename from packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java
rename to packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
index a6ae68f..afcf689 100644
--- a/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java
+++ b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.util;
+package android.utils;
 
 import android.annotation.NonNull;
 import android.os.Handler;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
new file mode 100644
index 0000000..e4923bf
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Bits and pieces copied from hidden API of android.os.FileUtils.
+ *
+ * @hide
+ */
+public class FileUtils {
+    /**
+     * Read a text file into a String, optionally limiting the length.
+     *
+     * @param file     to read (will not seek, so things like /proc files are OK)
+     * @param max      length (positive for head, negative of tail, 0 for no limit)
+     * @param ellipsis to add of the file was truncated (can be null)
+     * @return the contents of the file, possibly truncated
+     * @throws IOException if something goes wrong reading the file
+     * @hide
+     */
+    public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
+            @Nullable String ellipsis) throws IOException {
+        InputStream input = new FileInputStream(file);
+        // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
+        // input stream, bytes read not equal to buffer size is not necessarily the correct
+        // indication for EOF; but it is true for BufferedInputStream due to its implementation.
+        BufferedInputStream bis = new BufferedInputStream(input);
+        try {
+            long size = file.length();
+            if (max > 0 || (size > 0 && max == 0)) {  // "head" mode: read the first N bytes
+                if (size > 0 && (max == 0 || size < max)) max = (int) size;
+                byte[] data = new byte[max + 1];
+                int length = bis.read(data);
+                if (length <= 0) return "";
+                if (length <= max) return new String(data, 0, length);
+                if (ellipsis == null) return new String(data, 0, max);
+                return new String(data, 0, max) + ellipsis;
+            } else if (max < 0) {  // "tail" mode: keep the last N
+                int len;
+                boolean rolled = false;
+                byte[] last = null;
+                byte[] data = null;
+                do {
+                    if (last != null) rolled = true;
+                    byte[] tmp = last;
+                    last = data;
+                    data = tmp;
+                    if (data == null) data = new byte[-max];
+                    len = bis.read(data);
+                } while (len == data.length);
+
+                if (last == null && len <= 0) return "";
+                if (last == null) return new String(data, 0, len);
+                if (len > 0) {
+                    rolled = true;
+                    System.arraycopy(last, len, last, 0, last.length - len);
+                    System.arraycopy(data, 0, last, last.length - len, len);
+                }
+                if (ellipsis == null || !rolled) return new String(last);
+                return ellipsis + new String(last);
+            } else {  // "cat" mode: size unknown, read it all in streaming fashion
+                ByteArrayOutputStream contents = new ByteArrayOutputStream();
+                int len;
+                byte[] data = new byte[1024];
+                do {
+                    len = bis.read(data);
+                    if (len > 0) contents.write(data, 0, len);
+                } while (len == data.length);
+                return contents.toString();
+            }
+        } finally {
+            bis.close();
+            input.close();
+        }
+    }
+
+    /**
+     * Perform an fsync on the given FileOutputStream. The stream at this
+     * point must be flushed but not yet closed.
+     *
+     * @hide
+     */
+    public static boolean sync(FileOutputStream stream) {
+        try {
+            if (stream != null) {
+                stream.getFD().sync();
+            }
+            return true;
+        } catch (IOException e) {
+        }
+        return false;
+    }
+
+    /**
+     * List the files in the directory or return empty file.
+     *
+     * @hide
+     */
+    public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
+        return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
+            : ArrayUtils.EMPTY_FILE;
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
similarity index 98%
rename from packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java
rename to packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
index 948ebcca..fdb15e2 100644
--- a/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java
+++ b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.util;
+package android.utils;
 
 import android.annotation.NonNull;
 import android.os.Handler;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
new file mode 100644
index 0000000..5cdc253
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.utils;
+
+import libcore.util.EmptyArray;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
+ *
+ * @hide
+ */
+public class LongArrayQueue {
+
+    private long[] mValues;
+    private int mSize;
+    private int mHead;
+    private int mTail;
+
+    private long[] newUnpaddedLongArray(int num) {
+        return new long[num];
+    }
+    /**
+     * Initializes a queue with the given starting capacity.
+     *
+     * @param initialCapacity the capacity.
+     */
+    public LongArrayQueue(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mValues = EmptyArray.LONG;
+        } else {
+            mValues = newUnpaddedLongArray(initialCapacity);
+        }
+        mSize = 0;
+        mHead = mTail = 0;
+    }
+
+    /**
+     * Initializes a queue with default starting capacity.
+     */
+    public LongArrayQueue() {
+        this(16);
+    }
+
+    /** @hide */
+    public static int growSize(int currentSize) {
+        return currentSize <= 4 ? 8 : currentSize * 2;
+    }
+
+    private void grow() {
+        if (mSize < mValues.length) {
+            throw new IllegalStateException("Queue not full yet!");
+        }
+        final int newSize = growSize(mSize);
+        final long[] newArray = newUnpaddedLongArray(newSize);
+        final int r = mValues.length - mHead; // Number of elements on and to the right of head.
+        System.arraycopy(mValues, mHead, newArray, 0, r);
+        System.arraycopy(mValues, 0, newArray, r, mHead);
+        mValues = newArray;
+        mHead = 0;
+        mTail = mSize;
+    }
+
+    /**
+     * Returns the number of elements in the queue.
+     */
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * Removes all elements from this queue.
+     */
+    public void clear() {
+        mSize = 0;
+        mHead = mTail = 0;
+    }
+
+    /**
+     * Adds a value to the tail of the queue.
+     *
+     * @param value the value to be added.
+     */
+    public void addLast(long value) {
+        if (mSize == mValues.length) {
+            grow();
+        }
+        mValues[mTail] = value;
+        mTail = (mTail + 1) % mValues.length;
+        mSize++;
+    }
+
+    /**
+     * Removes an element from the head of the queue.
+     *
+     * @return the element at the head of the queue.
+     * @throws NoSuchElementException if the queue is empty.
+     */
+    public long removeFirst() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        final long ret = mValues[mHead];
+        mHead = (mHead + 1) % mValues.length;
+        mSize--;
+        return ret;
+    }
+
+    /**
+     * Returns the element at the given position from the head of the queue, where 0 represents the
+     * head of the queue.
+     *
+     * @param position the position from the head of the queue.
+     * @return the element found at the given position.
+     * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
+     *                                   {@code position} >= {@link #size()}
+     */
+    public long get(int position) {
+        if (position < 0 || position >= mSize) {
+            throw new IndexOutOfBoundsException("Index " + position
+                + " not valid for a queue of size " + mSize);
+        }
+        final int index = (mHead + position) % mValues.length;
+        return mValues[index];
+    }
+
+    /**
+     * Returns the element at the head of the queue, without removing it.
+     *
+     * @return the element at the head of the queue.
+     * @throws NoSuchElementException if the queue is empty
+     */
+    public long peekFirst() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        return mValues[mHead];
+    }
+
+    /**
+     * Returns the element at the tail of the queue.
+     *
+     * @return the element at the tail of the queue.
+     * @throws NoSuchElementException if the queue is empty.
+     */
+    public long peekLast() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
+        return mValues[index];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        if (mSize <= 0) {
+            return "{}";
+        }
+
+        final StringBuilder buffer = new StringBuilder(mSize * 64);
+        buffer.append('{');
+        buffer.append(get(0));
+        for (int i = 1; i < mSize; i++) {
+            buffer.append(", ");
+            buffer.append(get(i));
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
new file mode 100644
index 0000000..dbbef61
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.utils;
+
+import android.annotation.NonNull;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+import libcore.util.XmlObjectFactory;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java
+ *
+ * @hide
+ */
+public class XmlUtils {
+
+    private static final String STRING_ARRAY_SEPARATOR = ":";
+
+    /** @hide */
+    public static final void beginDocument(XmlPullParser parser, String firstElementName)
+            throws XmlPullParserException, IOException {
+        int type;
+        while ((type = parser.next()) != parser.START_TAG
+            && type != parser.END_DOCUMENT) {
+            // Do nothing
+        }
+
+        if (type != parser.START_TAG) {
+            throw new XmlPullParserException("No start tag found");
+        }
+
+        if (!parser.getName().equals(firstElementName)) {
+            throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
+                + ", expected " + firstElementName);
+        }
+    }
+
+    /** @hide */
+    public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
+            throws IOException, XmlPullParserException {
+        for (;;) {
+            int type = parser.next();
+            if (type == XmlPullParser.END_DOCUMENT
+                    || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
+                return false;
+            }
+            if (type == XmlPullParser.START_TAG
+                    && parser.getDepth() == outerDepth + 1) {
+                return true;
+            }
+        }
+    }
+
+    private static XmlPullParser newPullParser() {
+        try {
+            XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+            return parser;
+        } catch (XmlPullParserException e) {
+            throw new AssertionError();
+        }
+    }
+
+    /** @hide */
+    public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
+            throws IOException {
+        final byte[] magic = new byte[4];
+        if (in instanceof FileInputStream) {
+            try {
+                Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
+            } catch (ErrnoException e) {
+                throw e.rethrowAsIOException();
+            }
+        } else {
+            if (!in.markSupported()) {
+                in = new BufferedInputStream(in);
+            }
+            in.mark(8);
+            in.read(magic);
+            in.reset();
+        }
+
+        final TypedXmlPullParser xml;
+        xml = (TypedXmlPullParser) newPullParser();
+        try {
+            xml.setInput(in, "UTF_8");
+        } catch (XmlPullParserException e) {
+            throw new IOException(e);
+        }
+        return xml;
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 4f2fa79..6ba684d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -574,7 +574,7 @@
     ) {
         viewNode.autofillId?.let {
             val domain = viewNode.webDomain
-            val request = viewNode.credentialManagerRequest
+            val request = viewNode.pendingCredentialRequest
             if (domain != null && request != null) {
                 responseClientState.putBoolean(
                     WEBVIEW_REQUESTED_CREDENTIAL_KEY, true)
@@ -604,8 +604,8 @@
             sessionId: Int
     ): MutableList<CredentialOption> {
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
-        if (Flags.autofillCredmanDevIntegration() && viewNode.credentialManagerRequest != null) {
-            viewNode.credentialManagerRequest
+        if (Flags.autofillCredmanDevIntegration() && viewNode.pendingCredentialRequest != null) {
+            viewNode.pendingCredentialRequest
                     ?.getCredentialOptions()
                     ?.forEach { credentialOption ->
                 credentialOption.candidateQueryData
diff --git a/packages/CredentialManager/wear/res/values/strings.xml b/packages/CredentialManager/wear/res/values/strings.xml
index 9480e64..ee8bb78 100644
--- a/packages/CredentialManager/wear/res/values/strings.xml
+++ b/packages/CredentialManager/wear/res/values/strings.xml
@@ -21,9 +21,6 @@
   <!-- Title of a screen prompting if the user would like to use their saved passkey.
   [CHAR LIMIT=80] -->
   <string name="use_passkey_title">Use passkey?</string>
-  <!-- Title of a screen prompting if the user would like to use their saved passkey.
-[CHAR LIMIT=80] -->
-  <string name="use_sign_in_with_provider_title">Use your sign in for %1$s</string>
   <!-- Title of a screen prompting if the user would like to sign in with provider
   [CHAR LIMIT=80] -->
   <string name="use_password_title">Use password?</string>
@@ -35,6 +32,8 @@
   <string name="dialog_sign_in_options_button">Sign-in Options</string>
   <!-- Title for multiple credentials folded screen. [CHAR LIMIT=NONE] -->
   <string name="sign_in_options_title">Sign-in Options</string>
+  <!-- Provider settings list title. [CHAR LIMIT=NONE] -->
+  <string name="provider_list_title">Manage sign-ins</string>
   <!-- Title for multiple credentials screen. [CHAR LIMIT=NONE] -->
   <string name="choose_sign_in_title">Choose a sign in</string>
   <!-- Title for multiple credentials screen with only passkeys. [CHAR LIMIT=NONE] -->
diff --git a/packages/CredentialManager/wear/robotests/Android.bp b/packages/CredentialManager/wear/robotests/Android.bp
new file mode 100644
index 0000000..c0a1822
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/Android.bp
@@ -0,0 +1,28 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_robolectric_test {
+    name: "CredentialSelectorTests",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    // Include test libraries.
+    instrumentation_for: "ClockworkCredentialManager",
+    libs: [
+        "androidx.test.runner",
+        "androidx.test.ext.junit",
+        "kotlinx_coroutines_android",
+        "kotlinx_coroutines",
+        "kotlinx-coroutines-core",
+        "kotlinx_coroutines_test",
+        "mockito-robolectric-prebuilt",
+        "mockito-kotlin2",
+        "CredentialManagerShared",
+        "ClockworkCredentialManager",
+        "framework_graphics_flags_java_lib",
+    ],
+    java_resource_dirs: ["config"],
+    upstream: true,
+}
diff --git a/packages/CredentialManager/wear/robotests/config/robolectric.properties b/packages/CredentialManager/wear/robotests/config/robolectric.properties
new file mode 100644
index 0000000..140e42b
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/config/robolectric.properties
@@ -0,0 +1,16 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+sdk=NEWEST_SDK
+
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
new file mode 100644
index 0000000..3422d3d
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.credentialmanager
+
+import java.time.Instant
+import android.graphics.drawable.Drawable
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.model.get.ActionEntryInfo
+import com.android.credentialmanager.model.get.AuthenticationEntryInfo
+import com.android.credentialmanager.model.Request
+import androidx.test.filters.SmallTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.junit.runner.RunWith
+import com.android.credentialmanager.model.CredentialType
+import com.google.common.truth.Truth.assertThat
+import com.android.credentialmanager.ui.mappers.toGet
+import com.android.credentialmanager.model.get.ProviderInfo
+import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerUserNameEntries
+
+/** Unit tests for [CredentialSelectorUiStateGetMapper]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CredentialSelectorUiStateGetMapperTest {
+
+    private val mDrawable = mock<Drawable>()
+
+    private val actionEntryInfo =
+        ActionEntryInfo(
+            providerId = "",
+            entryKey = "",
+            entrySubkey = "",
+            pendingIntent = null,
+            fillInIntent = null,
+            title = "title",
+            icon = mDrawable,
+            subTitle = "subtitle",
+        )
+
+    private val authenticationEntryInfo =
+        AuthenticationEntryInfo(
+            providerId = "",
+            entryKey = "",
+            entrySubkey = "",
+            pendingIntent = null,
+            fillInIntent = null,
+            title = "title",
+            providerDisplayName = "",
+            icon = mDrawable,
+            isUnlockedAndEmpty = true,
+            isLastUnlocked = true
+        )
+
+    val passkeyCredentialEntryInfo =
+        createCredentialEntryInfo(credentialType = CredentialType.PASSKEY, userName = "userName")
+
+    val unknownCredentialEntryInfo =
+        createCredentialEntryInfo(credentialType = CredentialType.UNKNOWN, userName = "userName2")
+
+    val passwordCredentialEntryInfo =
+        createCredentialEntryInfo(credentialType = CredentialType.PASSWORD, userName = "userName")
+
+    val recentlyUsedPasskeyCredential =
+        createCredentialEntryInfo(credentialType =
+    CredentialType.PASSKEY, lastUsedTimeMillis = 2L, userName = "userName")
+
+    val recentlyUsedPasswordCredential =
+        createCredentialEntryInfo(credentialType =
+    CredentialType.PASSWORD, lastUsedTimeMillis = 2L, userName = "userName")
+
+    val credentialList1 = listOf(
+        passkeyCredentialEntryInfo,
+        passwordCredentialEntryInfo
+    )
+
+    val credentialList2 = listOf(
+        passkeyCredentialEntryInfo,
+        passwordCredentialEntryInfo,
+        recentlyUsedPasskeyCredential,
+        unknownCredentialEntryInfo,
+        recentlyUsedPasswordCredential
+    )
+
+    @Test
+    fun `On primary screen, just one account returns SingleEntry`() {
+        val getCredentialUiState = Request.Get(
+            token = null,
+            resultReceiver = null,
+            finalResponseReceiver = null,
+            providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = true)
+
+        assertThat(getCredentialUiState).isEqualTo(
+            CredentialSelectorUiState.Get.SingleEntry(passkeyCredentialEntryInfo)
+        ) // prefer passkey over password for selected credential
+    }
+
+    @Test
+    fun `On primary screen, multiple accounts returns SingleEntryPerAccount`() {
+        val getCredentialUiState = Request.Get(
+            token = null,
+            resultReceiver = null,
+            finalResponseReceiver = null,
+            providerInfos = listOf(createProviderInfo(listOf(passkeyCredentialEntryInfo,
+                unknownCredentialEntryInfo)))).toGet(isPrimary = true)
+
+        assertThat(getCredentialUiState).isEqualTo(
+            CredentialSelectorUiState.Get.SingleEntryPerAccount(
+                sortedEntries = listOf(
+                    passkeyCredentialEntryInfo, // userName
+                    unknownCredentialEntryInfo // userName2
+                ),
+                authenticationEntryList = listOf(authenticationEntryInfo)
+            )) // prefer passkey from account 1, then unknown from account 2
+    }
+
+    @Test
+    fun `On secondary screen, a MultipleEntry is returned`() {
+        val getCredentialUiState = Request.Get(
+            token = null,
+            resultReceiver = null,
+            finalResponseReceiver = null,
+            providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = false)
+
+        assertThat(getCredentialUiState).isEqualTo(
+            CredentialSelectorUiState.Get.MultipleEntry(
+                listOf(PerUserNameEntries("userName", listOf(
+                    passkeyCredentialEntryInfo,
+                    passwordCredentialEntryInfo))
+                ),
+                listOf(actionEntryInfo),
+                listOf(authenticationEntryInfo)
+            ))
+    }
+
+    @Test
+    fun `Returned multiple entry is sorted by credentialType and lastUsedTimeMillis`() {
+        val getCredentialUiState = Request.Get(
+            token = null,
+            resultReceiver = null,
+            finalResponseReceiver = null,
+            providerInfos = listOf(createProviderInfo(credentialList1),
+                createProviderInfo(credentialList2))).toGet(isPrimary = false)
+
+        assertThat(getCredentialUiState).isEqualTo(
+            CredentialSelectorUiState.Get.MultipleEntry(
+                listOf(
+                    PerUserNameEntries("userName",
+                        listOf(
+                            recentlyUsedPasskeyCredential, // from provider 2
+                            passkeyCredentialEntryInfo, // from provider 1 or 2
+                            passkeyCredentialEntryInfo, // from provider 1 or 2
+                            recentlyUsedPasswordCredential, // from provider 2
+                            passwordCredentialEntryInfo, // from provider 1 or 2
+                            passwordCredentialEntryInfo, // from provider 1 or 2
+                        )),
+                    PerUserNameEntries("userName2", listOf(unknownCredentialEntryInfo)),
+                ),
+                listOf(actionEntryInfo, actionEntryInfo),
+                listOf(authenticationEntryInfo, authenticationEntryInfo)
+            )
+        )
+    }
+
+    fun createCredentialEntryInfo(
+        userName: String,
+        credentialType: CredentialType = CredentialType.PASSKEY,
+        lastUsedTimeMillis: Long = 0L
+    ): CredentialEntryInfo =
+        CredentialEntryInfo(
+            providerId = "",
+            entryKey = "",
+            entrySubkey = "",
+            pendingIntent = null,
+            fillInIntent = null,
+            credentialType = credentialType,
+            rawCredentialType = "",
+            credentialTypeDisplayName = "",
+            providerDisplayName = "",
+            userName = userName,
+            displayName = "",
+            icon = mDrawable,
+            shouldTintIcon = false,
+            lastUsedTimeMillis = Instant.ofEpochMilli(lastUsedTimeMillis),
+            isAutoSelectable = true,
+            entryGroupId = "",
+            isDefaultIconPreferredAsSingleProvider = false,
+            affiliatedDomain = "",
+        )
+
+    fun createProviderInfo(credentials: List<CredentialEntryInfo> = listOf()): ProviderInfo =
+        ProviderInfo(
+            id = "providerInfo",
+            icon = mDrawable,
+            displayName = "displayName",
+            credentialEntryList = credentials,
+            authenticationEntryList = listOf(authenticationEntryInfo),
+            remoteEntry = null,
+            actionEntryList = listOf(actionEntryInfo)
+        )
+}
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
new file mode 100644
index 0000000..b79f34c
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.credentialmanager
+
+import org.mockito.kotlin.whenever
+import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.model.Request
+import androidx.test.filters.SmallTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.Before
+import java.util.Collections.emptyList
+import org.junit.runner.RunWith
+import android.content.Intent
+import com.android.credentialmanager.client.CredentialManagerClient
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import android.credentials.selection.BaseDialogResult
+import com.google.common.truth.Truth.assertThat
+import org.mockito.kotlin.doReturn
+import kotlinx.coroutines.Job
+import org.junit.After
+import org.robolectric.shadows.ShadowLooper
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Unit tests for [CredentialSelectorViewModel]. */
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CredentialSelectorViewModelTest {
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+
+    private val stateFlow: MutableStateFlow<Request?> = MutableStateFlow(Request.Create(null))
+    private val credentialManagerClient = mock<CredentialManagerClient>{
+        on { requests } doReturn stateFlow
+    }
+    private val mViewModel = CredentialSelectorViewModel(credentialManagerClient)
+    private lateinit var job: Job
+
+    val testEntryInfo =
+        EntryInfo(
+            providerId = "",
+            entryKey = "",
+            entrySubkey = "",
+            pendingIntent = null,
+            fillInIntent = null,
+            shouldTerminateUiUponSuccessfulProviderResult = true)
+
+    @Before
+    fun setUp() {
+      job = checkNotNull(mViewModel).uiState.launchIn(testScope)
+    }
+
+    @After
+    fun teardown() {
+        job.cancel()
+    }
+
+    @Test
+    fun `Setting state to idle when receiving null request`() {
+        stateFlow.value = null
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Idle)
+    }
+
+    @Test
+    fun `Setting state to cancel when receiving Cancel request`() {
+        stateFlow.value = Request.Cancel(appName = "appName", token = null)
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value)
+            .isEqualTo(CredentialSelectorUiState.Cancel("appName"))
+    }
+
+    @Test
+    fun `Setting state to create when receiving Create request`() {
+        stateFlow.value = Request.Create(token = null)
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Create)
+    }
+
+    @Test
+    fun `Closing app when receiving Close request`() {
+        stateFlow.value = Request.Close(token = null)
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Updates request`() {
+        val intent = Intent()
+
+        mViewModel.updateRequest(intent)
+
+        verify(credentialManagerClient).updateRequest(intent)
+    }
+
+    @Test
+    fun `Back on a single entry screen closes app`() {
+        mViewModel.openSecondaryScreen()
+        stateFlow.value = Request.Get(
+            token = null,
+            resultReceiver = null,
+            finalResponseReceiver = null,
+            providerInfos = emptyList())
+
+        mViewModel.back()
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Back on a multiple entry screen gets us back to a primary screen`() {
+        mViewModel.openSecondaryScreen()
+        stateFlow.value = Request.Get(
+            token = null,
+            resultReceiver = null,
+            finalResponseReceiver = null,
+            providerInfos = emptyList())
+
+        mViewModel.back()
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Back on create request state closes app`() {
+        stateFlow.value = Request.Create(token = null)
+
+        mViewModel.back()
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Back on close request state closes app`() {
+        stateFlow.value = Request.Close(token = null)
+
+        mViewModel.back()
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Back on cancel request state closes app`() {
+        stateFlow.value = Request.Cancel(appName = "", token = null)
+
+        mViewModel.back()
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Back on idle request state closes app`() {
+        stateFlow.value = null
+
+        mViewModel.back()
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Cancel closes the app`() {
+        mViewModel.cancel()
+        ShadowLooper.idleMainLooper()
+
+        verify(credentialManagerClient).sendError(BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED)
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Send entry selection result closes app and calls client method`() {
+        whenever(credentialManagerClient.sendEntrySelectionResult(
+            entryInfo = testEntryInfo,
+            resultCode = null,
+            resultData = null,
+            isAutoSelected = false
+        )).thenReturn(true)
+
+        mViewModel.sendSelectionResult(
+            entryInfo = testEntryInfo,
+            resultCode = null,
+            resultData = null,
+            isAutoSelected = false)
+        ShadowLooper.idleMainLooper()
+
+        verify(credentialManagerClient).sendEntrySelectionResult(
+            testEntryInfo, null, null, false
+        )
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Send entry selection result does not close app on false return`() {
+        whenever(credentialManagerClient.sendEntrySelectionResult(
+            entryInfo = testEntryInfo,
+            resultCode = null,
+            resultData = null,
+            isAutoSelected = false
+        )).thenReturn(false)
+        stateFlow.value = Request.Create(null)
+
+        mViewModel.sendSelectionResult(entryInfo = testEntryInfo, resultCode = null,
+            resultData = null, isAutoSelected = false)
+        ShadowLooper.idleMainLooper()
+
+        verify(credentialManagerClient).sendEntrySelectionResult(
+            entryInfo = testEntryInfo,
+            resultCode = null,
+            resultData = null,
+            isAutoSelected = false
+        )
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Create)
+    }
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 66be7ba..9d97763 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -59,8 +59,10 @@
             isPrimaryScreen,
             shouldClose
         ) { request, isPrimary, shouldClose ->
+            Log.d(TAG, "Request updated: " + request?.toString() +
+                    " isClose: " + shouldClose.toString() +
+                    " isPrimaryScreen: " + isPrimary.toString())
             if (shouldClose) {
-                Log.d(TAG, "Request finished, closing ")
                 return@combine Close
             }
 
@@ -139,7 +141,10 @@
     data object Idle : CredentialSelectorUiState()
     sealed class Get : CredentialSelectorUiState() {
         data class SingleEntry(val entry: CredentialEntryInfo) : Get()
-        data class SingleEntryPerAccount(val sortedEntries: List<CredentialEntryInfo>) : Get()
+        data class SingleEntryPerAccount(
+            val sortedEntries: List<CredentialEntryInfo>,
+            val authenticationEntryList: List<AuthenticationEntryInfo>,
+            ) : Get()
         data class MultipleEntry(
             val accounts: List<PerUserNameEntries>,
             val actionEntryList: List<ActionEntryInfo>,
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index 405de1d3..bf4c988 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -29,6 +29,7 @@
 import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
 import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState
 import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntryPerAccount
 import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
 import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
 import com.android.credentialmanager.CredentialSelectorViewModel
@@ -45,6 +46,8 @@
 import com.android.credentialmanager.model.CredentialType
 import com.android.credentialmanager.model.EntryInfo
 import com.android.credentialmanager.ui.screens.multiple.MultiCredentialsFoldScreen
+import com.android.credentialmanager.ui.screens.multiple.MultiCredentialsFlattenScreen
+
 
 @OptIn(ExperimentalHorologistApi::class)
 @Composable
@@ -78,59 +81,70 @@
 
         scrollable(Screen.SinglePasskeyScreen.route) {
             SinglePasskeyScreen(
-                credentialSelectorUiState = viewModel.uiState.value as SingleEntry,
+                entry = (remember { uiState } as SingleEntry).entry,
                 columnState = it.columnState,
+                flowEngine = flowEngine,
             )
         }
 
         scrollable(Screen.SignInWithProviderScreen.route) {
             SignInWithProviderScreen(
-                credentialSelectorUiState = viewModel.uiState.value as SingleEntry,
+                entry = (remember { uiState } as SingleEntry).entry,
                 columnState = it.columnState,
+                flowEngine = flowEngine,
             )
         }
 
         scrollable(Screen.MultipleCredentialsScreenFold.route) {
             MultiCredentialsFoldScreen(
-                credentialSelectorUiState = viewModel.uiState.value as MultipleEntry,
-                screenIcon = null,
+                credentialSelectorUiState = (remember { uiState } as SingleEntryPerAccount),
                 columnState = it.columnState,
+                flowEngine = flowEngine,
+            )
+        }
+
+        scrollable(Screen.MultipleCredentialsScreenFlatten.route) {
+            MultiCredentialsFlattenScreen(
+                credentialSelectorUiState = (remember { uiState } as MultipleEntry),
+                columnState = it.columnState,
+                flowEngine = flowEngine,
             )
         }
     }
-    BackHandler(true) {
-        viewModel.back()
-    }
-    Log.d(TAG, "uiState change, state: $uiState")
-    when (val state = uiState) {
-        CredentialSelectorUiState.Idle -> {
-            if (navController.currentDestination?.route != Screen.Loading.route) {
-                navController.navigateToLoading()
+        BackHandler(true) {
+            viewModel.back()
+        }
+        Log.d(TAG, "uiState change, state: $uiState")
+        when (val state = uiState) {
+            CredentialSelectorUiState.Idle -> {
+                if (navController.currentDestination?.route != Screen.Loading.route) {
+                    navController.navigateToLoading()
+                }
+            }
+
+            is CredentialSelectorUiState.Get -> {
+                handleGetNavigation(
+                    navController = navController,
+                    state = state,
+                    onCloseApp = onCloseApp,
+                    selectEntry = selectEntry
+                )
+            }
+
+            CredentialSelectorUiState.Create -> {
+                // TODO: b/301206624 - Implement create flow
+                onCloseApp()
+            }
+
+            is CredentialSelectorUiState.Cancel -> {
+                onCloseApp()
+            }
+
+            CredentialSelectorUiState.Close -> {
+                onCloseApp()
             }
         }
-        is CredentialSelectorUiState.Get -> {
-            handleGetNavigation(
-                navController = navController,
-                state = state,
-                onCloseApp = onCloseApp,
-                selectEntry = selectEntry
-            )
-        }
-
-        CredentialSelectorUiState.Create -> {
-            // TODO: b/301206624 - Implement create flow
-            onCloseApp()
-        }
-
-        is CredentialSelectorUiState.Cancel -> {
-            onCloseApp()
-        }
-
-        CredentialSelectorUiState.Close -> {
-            onCloseApp()
-        }
     }
-}
 
 private fun handleGetNavigation(
     navController: NavController,
@@ -157,13 +171,12 @@
             }
         }
 
-        is MultipleEntry -> {
-            navController.navigateToMultipleCredentialsFoldScreen()
-        }
+            is SingleEntryPerAccount -> {
+                navController.navigateToMultipleCredentialsFoldScreen()
+            }
 
-        else -> {
-            // TODO: b/301206470 - Implement other get flows
-            onCloseApp()
+            is MultipleEntry -> {
+                navController.navigateToMultipleCredentialsFlattenScreen()
+            }
         }
     }
-}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
index 03b0931..7a936b6 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
@@ -32,11 +32,14 @@
     return if (isPrimary) {
         if (accounts.size == 1) {
             CredentialSelectorUiState.Get.SingleEntry(
-                accounts[0].value.minWith(comparator)
+                entry = accounts[0].value.minWith(comparator)
             )
         } else {
             CredentialSelectorUiState.Get.SingleEntryPerAccount(
-                accounts.map { it.value.minWith(comparator) }.sortedWith(comparator)
+                sortedEntries = accounts.map {
+                    it.value.minWith(comparator)
+                }.sortedWith(comparator),
+                authenticationEntryList = providerInfos.flatMap { it.authenticationEntryList }
             )
         }
     } else {
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
index 11188b4..d54103c 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
@@ -15,112 +15,53 @@
  */
 package com.android.credentialmanager.ui.screens.multiple
 
-import android.graphics.drawable.Drawable
-import com.android.credentialmanager.ui.screens.UiState
-import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
 import androidx.wear.compose.material.MaterialTheme
 import androidx.wear.compose.material.Text
 import com.android.credentialmanager.ui.components.SignInHeader
 import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
+import com.android.credentialmanager.FlowEngine
 import com.android.credentialmanager.R
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
-import com.android.credentialmanager.model.get.ActionEntryInfo
 import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.ui.components.CredentialsScreenChip
 import com.google.android.horologist.annotations.ExperimentalHorologistApi
 import com.google.android.horologist.compose.layout.ScalingLazyColumn
 import com.google.android.horologist.compose.layout.ScalingLazyColumnState
 
-
 /**
  * Screen that shows multiple credentials to select from, grouped by accounts
  *
  * @param credentialSelectorUiState The app bar view model.
- * @param screenIcon The view model corresponding to the home page.
  * @param columnState ScalingLazyColumn configuration to be be applied
  * @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
+ * @param flowEngine [FlowEngine] that updates ui state for this screen
  */
 @OptIn(ExperimentalHorologistApi::class)
 @Composable
 fun MultiCredentialsFlattenScreen(
     credentialSelectorUiState: MultipleEntry,
-    screenIcon: Drawable?,
     columnState: ScalingLazyColumnState,
-    modifier: Modifier = Modifier,
-    viewModel: MultiCredentialsFlattenViewModel = hiltViewModel(),
-    navController: NavHostController = rememberNavController(),
+    flowEngine: FlowEngine,
 ) {
-    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
-    when (val state = uiState) {
-        UiState.CredentialScreen -> {
-            MultiCredentialsFlattenScreen(
-                state = credentialSelectorUiState,
-                columnState = columnState,
-                screenIcon = screenIcon,
-                onActionEntryClicked = viewModel::onActionEntryClicked,
-                onCredentialClicked = viewModel::onCredentialClicked,
-                modifier = modifier,
-            )
-        }
-
-        is UiState.CredentialSelected -> {
-            val launcher = rememberLauncherForActivityResult(
-                StartBalIntentSenderForResultContract()
-            ) {
-                viewModel.onInfoRetrieved(it.resultCode, null)
-            }
-
-            SideEffect {
-                state.intentSenderRequest?.let {
-                    launcher.launch(it)
-                }
-            }
-        }
-
-        UiState.Cancel -> {
-            navController.popBackStack()
-        }
-    }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun MultiCredentialsFlattenScreen(
-    state: MultipleEntry,
-    columnState: ScalingLazyColumnState,
-    screenIcon: Drawable?,
-    onActionEntryClicked: (entryInfo: ActionEntryInfo) -> Unit,
-    onCredentialClicked: (entryInfo: CredentialEntryInfo) -> Unit,
-    modifier: Modifier,
-) {
+    val selectEntry = flowEngine.getEntrySelector()
     ScalingLazyColumn(
         columnState = columnState,
-        modifier = modifier.fillMaxSize(),
+        modifier = Modifier.fillMaxSize(),
     ) {
         item {
             // make this credential specific if all credentials are same
             SignInHeader(
-                icon = screenIcon,
+                icon = null,
                 title = stringResource(R.string.sign_in_options_title),
             )
         }
 
-        state.accounts.forEach { userNameEntries ->
+        credentialSelectorUiState.accounts.forEach { userNameEntries ->
             item {
                 Text(
                     text = userNameEntries.userName,
@@ -135,17 +76,16 @@
                 item {
                     CredentialsScreenChip(
                         label = credential.userName,
-                        onClick = { onCredentialClicked(credential) },
-                        secondaryLabel = credential.userName,
+                        onClick = { selectEntry(credential, false) },
+                        secondaryLabel = credential.credentialTypeDisplayName,
                         icon = credential.icon,
-                        modifier = modifier,
                     )
                 }
             }
         }
         item {
             Text(
-                text = "Manage Sign-ins",
+                text = stringResource(R.string.provider_list_title),
                 modifier = Modifier
                     .padding(top = 6.dp)
                     .padding(horizontal = 10.dp),
@@ -153,14 +93,13 @@
             )
         }
 
-        state.actionEntryList.forEach {
+        credentialSelectorUiState.actionEntryList.forEach {actionEntry ->
             item {
                     CredentialsScreenChip(
-                        label = it.title,
-                        onClick = { onActionEntryClicked(it) },
+                        label = actionEntry.title,
+                        onClick = { selectEntry(actionEntry, false) },
                         secondaryLabel = null,
-                        icon = it.icon,
-                        modifier = modifier,
+                        icon = actionEntry.icon,
                     )
             }
         }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenViewModel.kt
deleted file mode 100644
index ee5f3f4..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenViewModel.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.screens.multiple
-
-import android.content.Intent
-import android.credentials.selection.ProviderPendingIntentResponse
-import android.credentials.selection.UserSelectionDialogResult
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.model.get.ActionEntryInfo
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.screens.UiState
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-/** ViewModel for [MultiCredentialsFlattenScreen].*/
-@HiltViewModel
-class MultiCredentialsFlattenViewModel @Inject constructor(
-    private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
-    private lateinit var requestGet: Request.Get
-    private lateinit var entryInfo: CredentialEntryInfo
-
-    private val _uiState =
-        MutableStateFlow<UiState>(UiState.CredentialScreen)
-    val uiState: StateFlow<UiState> = _uiState
-
-    fun onCredentialClicked(entryInfo: CredentialEntryInfo) {
-        this.entryInfo = entryInfo
-        _uiState.value = UiState.CredentialSelected(
-            intentSenderRequest = entryInfo.getIntentSenderRequest()
-        )
-    }
-
-    fun onCancelClicked() {
-        _uiState.value = UiState.Cancel
-    }
-
-    fun onInfoRetrieved(
-        resultCode: Int? = null,
-        resultData: Intent? = null,
-    ) {
-        val userSelectionDialogResult = UserSelectionDialogResult(
-            requestGet.token,
-            entryInfo.providerId,
-            entryInfo.entryKey,
-            entryInfo.entrySubkey,
-            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
-        )
-        credentialManagerClient.sendResult(userSelectionDialogResult)
-    }
-
-    fun onActionEntryClicked(actionEntryInfo: ActionEntryInfo) {
-        // TODO(b/322797032)to be filled out
-    }
-}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
index 5515c86..6f32c99 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
@@ -16,24 +16,15 @@
 
 package com.android.credentialmanager.ui.screens.multiple
 
-import com.android.credentialmanager.ui.screens.UiState
-import android.graphics.drawable.Drawable
-import androidx.activity.compose.rememberLauncherForActivityResult
 import com.android.credentialmanager.R
 import androidx.compose.ui.res.stringResource
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
 import com.android.credentialmanager.CredentialSelectorUiState
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
+import com.android.credentialmanager.FlowEngine
 import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.ui.components.DismissChip
 import com.android.credentialmanager.ui.components.CredentialsScreenChip
@@ -49,74 +40,22 @@
  * Screen that shows multiple credentials to select from.
  *
  * @param credentialSelectorUiState The app bar view model.
- * @param screenIcon The view model corresponding to the home page.
  * @param columnState ScalingLazyColumn configuration to be be applied
- * @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
  */
 @OptIn(ExperimentalHorologistApi::class)
 @Composable
 fun MultiCredentialsFoldScreen(
-    credentialSelectorUiState: CredentialSelectorUiState.Get.MultipleEntry,
-    screenIcon: Drawable?,
+    credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntryPerAccount,
     columnState: ScalingLazyColumnState,
-    modifier: Modifier = Modifier,
-    viewModel: MultiCredentialsFoldViewModel = hiltViewModel(),
-    navController: NavHostController = rememberNavController(),
+    flowEngine: FlowEngine,
 ) {
-    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
-    when (val state = uiState) {
-        UiState.CredentialScreen -> {
-            MultiCredentialsFoldScreen(
-                state = credentialSelectorUiState,
-                onSignInOptionsClicked = viewModel::onSignInOptionsClicked,
-                onCredentialClicked = viewModel::onCredentialClicked,
-                onCancelClicked = viewModel::onCancelClicked,
-                screenIcon = screenIcon,
-                columnState = columnState,
-                modifier = modifier
-            )
-        }
-
-        is UiState.CredentialSelected -> {
-            val launcher = rememberLauncherForActivityResult(
-                StartBalIntentSenderForResultContract()
-            ) {
-                viewModel.onInfoRetrieved(it.resultCode, null)
-            }
-
-            SideEffect {
-                state.intentSenderRequest?.let {
-                    launcher.launch(it)
-                }
-            }
-        }
-
-        UiState.Cancel -> {
-            navController.popBackStack()
-        }
-    }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun MultiCredentialsFoldScreen(
-    state: CredentialSelectorUiState.Get.MultipleEntry,
-    onSignInOptionsClicked: () -> Unit,
-    onCredentialClicked: (entryInfo: CredentialEntryInfo) -> Unit,
-    onCancelClicked: () -> Unit,
-    screenIcon: Drawable?,
-    columnState: ScalingLazyColumnState,
-    modifier: Modifier,
-) {
+    val selectEntry = flowEngine.getEntrySelector()
     ScalingLazyColumn(
         columnState = columnState,
-        modifier = modifier.fillMaxSize(),
+        modifier = Modifier.fillMaxSize(),
     ) {
         // flatten all credentials into one
-        val credentials = state.accounts.flatMap { it.sortedCredentialEntryList }
+        val credentials = credentialSelectorUiState.sortedEntries
         item {
             var title = stringResource(R.string.choose_sign_in_title)
             if (credentials.all{ it.credentialType == CredentialType.PASSKEY }) {
@@ -126,7 +65,7 @@
             }
 
             SignInHeader(
-                icon = screenIcon,
+                icon = null,
                 title = title,
                 modifier = Modifier
                     .padding(top = 6.dp),
@@ -137,22 +76,24 @@
                 item {
                     CredentialsScreenChip(
                         label = credential.userName,
-                        onClick = { onCredentialClicked(credential) },
+                        onClick = { selectEntry(credential, false) },
                         secondaryLabel = credential.credentialTypeDisplayName,
                         icon = credential.icon,
                     )
                 }
             }
 
-        state.authenticationEntryList.forEach { authenticationEntryInfo ->
+        credentialSelectorUiState.authenticationEntryList.forEach { authenticationEntryInfo ->
             item {
                 LockedProviderChip(authenticationEntryInfo) {
-                    // TODO(b/322797032) invoke LockedProviderScreen here using flow engine
-                }
+                    selectEntry(authenticationEntryInfo, false) }
             }
         }
-
-        item { SignInOptionsChip(onSignInOptionsClicked)}
-        item { DismissChip(onCancelClicked) }
+        item {
+            SignInOptionsChip { flowEngine.openSecondaryScreen() }
+        }
+        item {
+            DismissChip { flowEngine.cancel() }
+        }
     }
 }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt
deleted file mode 100644
index 627a63d..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.screens.multiple
-
-import android.content.Intent
-import android.credentials.selection.ProviderPendingIntentResponse
-import android.credentials.selection.UserSelectionDialogResult
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.screens.UiState
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-/** ViewModel for [MultiCredentialsFoldScreen].*/
-@HiltViewModel
-class MultiCredentialsFoldViewModel @Inject constructor(
-    private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
-    private lateinit var requestGet: Request.Get
-    private lateinit var entryInfo: CredentialEntryInfo
-
-    private val _uiState =
-        MutableStateFlow<UiState>(UiState.CredentialScreen)
-    val uiState: StateFlow<UiState> = _uiState
-
-    fun onCredentialClicked(entryInfo: CredentialEntryInfo) {
-        this.entryInfo = entryInfo
-        _uiState.value = UiState.CredentialSelected(
-            intentSenderRequest = entryInfo.getIntentSenderRequest()
-        )
-    }
-
-    fun onSignInOptionsClicked() {
-        // TODO(b/322797032) Implement navigation route for single credential screen to multiple
-        // credentials
-    }
-
-    fun onCancelClicked() {
-        _uiState.value = UiState.Cancel
-    }
-
-    fun onInfoRetrieved(
-        resultCode: Int? = null,
-        resultData: Intent? = null,
-    ) {
-        val userSelectionDialogResult = UserSelectionDialogResult(
-            requestGet.token,
-            entryInfo.providerId,
-            entryInfo.entryKey,
-            entryInfo.entrySubkey,
-            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
-        )
-        credentialManagerClient.sendResult(userSelectionDialogResult)
-    }
-}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index b2595a1..e79176b 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -19,122 +19,62 @@
 package com.android.credentialmanager.ui.screens.single.passkey
 
 import androidx.compose.foundation.layout.Column
-import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
-import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.FlowEngine
 import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.R
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
 import com.android.credentialmanager.ui.components.AccountRow
 import com.android.credentialmanager.ui.components.ContinueChip
 import com.android.credentialmanager.ui.components.DismissChip
 import com.android.credentialmanager.ui.components.SignInHeader
 import com.android.credentialmanager.ui.components.SignInOptionsChip
 import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
-import com.android.credentialmanager.ui.screens.UiState
 import com.google.android.horologist.annotations.ExperimentalHorologistApi
 import com.google.android.horologist.compose.layout.ScalingLazyColumnState
 
 /**
  * Screen that shows sign in with provider credential.
  *
- * @param credentialSelectorUiState The app bar view model.
+ * @param entry The password entry
  * @param columnState ScalingLazyColumn configuration to be be applied to SingleAccountScreen
  * @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
+ * @param flowEngine [FlowEngine] that updates ui state for this screen
  */
 @OptIn(ExperimentalHorologistApi::class)
 @Composable
 fun SinglePasskeyScreen(
-    credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
-    columnState: ScalingLazyColumnState,
-    modifier: Modifier = Modifier,
-    viewModel: SinglePasskeyScreenViewModel = hiltViewModel(),
-    navController: NavHostController = rememberNavController(),
-) {
-    viewModel.initialize(credentialSelectorUiState.entry)
-
-    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
-    when (val state = uiState) {
-        UiState.CredentialScreen -> {
-            SinglePasskeyScreen(
-                credentialSelectorUiState.entry,
-                columnState,
-                modifier,
-                viewModel
-            )
-        }
-
-        is UiState.CredentialSelected -> {
-            val launcher = rememberLauncherForActivityResult(
-                StartBalIntentSenderForResultContract()
-            ) {
-                viewModel.onPasskeyInfoRetrieved(it.resultCode, null)
-            }
-
-            SideEffect {
-                state.intentSenderRequest?.let {
-                    launcher.launch(it)
-                }
-            }
-        }
-
-        UiState.Cancel -> {
-            // TODO(b/322797032) add valid navigation path here for going back
-            navController.popBackStack()
-        }
-    }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun SinglePasskeyScreen(
     entry: CredentialEntryInfo,
     columnState: ScalingLazyColumnState,
     modifier: Modifier = Modifier,
-    viewModel: SinglePasskeyScreenViewModel,
+    flowEngine: FlowEngine,
 ) {
     SingleAccountScreen(
         headerContent = {
             SignInHeader(
                 icon = entry.icon,
-                title = stringResource(R.string.use_passkey_title),
+                title = stringResource(R.string.use_password_title),
             )
         },
         accountContent = {
-            if (entry.displayName != null) {
-                AccountRow(
+            AccountRow(
                     primaryText = checkNotNull(entry.displayName),
                     secondaryText = entry.userName,
                     modifier = Modifier.padding(top = 10.dp),
                 )
-            } else {
-                AccountRow(
-                    primaryText = entry.userName,
-                    modifier = Modifier.padding(top = 10.dp),
-                )
-            }
         },
         columnState = columnState,
         modifier = modifier.padding(horizontal = 10.dp)
     ) {
         item {
+            val selectEntry = flowEngine.getEntrySelector()
             Column {
-                ContinueChip(viewModel::onContinueClick)
-                SignInOptionsChip(viewModel::onSignInOptionsClick)
-                DismissChip(viewModel::onDismissClick)
+                ContinueChip { selectEntry(entry, false) }
+                SignInOptionsChip{ flowEngine.openSecondaryScreen() }
+                DismissChip { flowEngine.cancel() }
             }
         }
     }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt
deleted file mode 100644
index 37ffaca..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.screens.single.passkey
-
-import android.content.Intent
-import android.credentials.selection.UserSelectionDialogResult
-import android.credentials.selection.ProviderPendingIntentResponse
-import androidx.annotation.MainThread
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import dagger.hilt.android.lifecycle.HiltViewModel
-import com.android.credentialmanager.ui.screens.UiState
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-@HiltViewModel
-class SinglePasskeyScreenViewModel @Inject constructor(
-    private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
-    private val _uiState =
-        MutableStateFlow<UiState>(UiState.CredentialScreen)
-    val uiState: StateFlow<UiState> = _uiState
-
-    private lateinit var requestGet: Request.Get
-    private lateinit var entryInfo: CredentialEntryInfo
-
-
-    @MainThread
-    fun initialize(entry: CredentialEntryInfo) {
-        this.entryInfo = entry
-    }
-
-    fun onDismissClick() {
-        _uiState.value = UiState.Cancel
-    }
-
-    fun onContinueClick() {
-        _uiState.value = UiState.CredentialSelected(
-            intentSenderRequest = entryInfo.getIntentSenderRequest()
-        )
-    }
-
-    fun onSignInOptionsClick() {
-    }
-
-    fun onPasskeyInfoRetrieved(
-        resultCode: Int? = null,
-        resultData: Intent? = null,
-    ) {
-        val userSelectionDialogResult = UserSelectionDialogResult(
-            requestGet.token,
-            entryInfo.providerId,
-            entryInfo.entryKey,
-            entryInfo.entrySubkey,
-            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
-        )
-        credentialManagerClient.sendResult(userSelectionDialogResult)
-    }
-}
-
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
index b0ece0d..3a86feb 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
@@ -16,100 +16,43 @@
 
 package com.android.credentialmanager.ui.screens.single.signInWithProvider
 
-import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
-import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.FlowEngine
 import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.R
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
 import com.android.credentialmanager.ui.components.AccountRow
 import com.android.credentialmanager.ui.components.ContinueChip
 import com.android.credentialmanager.ui.components.DismissChip
 import com.android.credentialmanager.ui.components.SignInHeader
 import com.android.credentialmanager.ui.components.SignInOptionsChip
 import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
-import com.android.credentialmanager.ui.screens.UiState
 import com.google.android.horologist.annotations.ExperimentalHorologistApi
 import com.google.android.horologist.compose.layout.ScalingLazyColumnState
 
 /**
  * Screen that shows sign in with provider credential.
  *
- * @param credentialSelectorUiState The app bar view model.
+ * @param entry The password entry.
  * @param columnState ScalingLazyColumn configuration to be be applied to SingleAccountScreen
  * @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
+ * @param flowEngine [FlowEngine] that updates ui state for this screen
  */
 @OptIn(ExperimentalHorologistApi::class)
 @Composable
 fun SignInWithProviderScreen(
-    credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
-    columnState: ScalingLazyColumnState,
-    modifier: Modifier = Modifier,
-    viewModel: SignInWithProviderViewModel = hiltViewModel(),
-    navController: NavHostController = rememberNavController(),
-) {
-    viewModel.initialize(credentialSelectorUiState.entry)
-
-    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
-    when (uiState) {
-        UiState.CredentialScreen -> {
-            SignInWithProviderScreen(
-                credentialSelectorUiState.entry,
-                columnState,
-                modifier,
-                viewModel
-            )
-        }
-
-        is UiState.CredentialSelected -> {
-            val launcher = rememberLauncherForActivityResult(
-                StartBalIntentSenderForResultContract()
-            ) {
-                viewModel.onInfoRetrieved(it.resultCode, null)
-            }
-
-            SideEffect {
-                (uiState as UiState.CredentialSelected).intentSenderRequest?.let {
-                    launcher.launch(it)
-                }
-            }
-        }
-
-        UiState.Cancel -> {
-            // TODO(b/322797032) add valid navigation path here for going back
-            navController.popBackStack()
-        }
-    }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun SignInWithProviderScreen(
     entry: CredentialEntryInfo,
     columnState: ScalingLazyColumnState,
     modifier: Modifier = Modifier,
-    viewModel: SignInWithProviderViewModel,
+    flowEngine: FlowEngine,
 ) {
     SingleAccountScreen(
         headerContent = {
             SignInHeader(
                 icon = entry.icon,
-                title = stringResource(R.string.use_sign_in_with_provider_title,
-                    entry.providerDisplayName),
+                title = entry.providerDisplayName,
             )
         },
         accountContent = {
@@ -130,12 +73,13 @@
         columnState = columnState,
         modifier = modifier.padding(horizontal = 10.dp)
     ) {
-       item {
-           Column {
-               ContinueChip(viewModel::onContinueClick)
-               SignInOptionsChip(viewModel::onSignInOptionsClick)
-               DismissChip(viewModel::onDismissClick)
-           }
-       }
+        item {
+            val selectEntry = flowEngine.getEntrySelector()
+            Column {
+                ContinueChip { selectEntry(entry, false) }
+                SignInOptionsChip{ flowEngine.openSecondaryScreen() }
+                DismissChip { flowEngine.cancel() }
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt
deleted file mode 100644
index 7ba45e5..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.screens.single.signInWithProvider
-
-import android.content.Intent
-import android.credentials.selection.ProviderPendingIntentResponse
-import android.credentials.selection.UserSelectionDialogResult
-import androidx.annotation.MainThread
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import dagger.hilt.android.lifecycle.HiltViewModel
-import com.android.credentialmanager.ui.screens.UiState
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-/** ViewModel for [SignInWithProviderScreen].*/
-@HiltViewModel
-class SignInWithProviderViewModel @Inject constructor(
-    private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
-    private val _uiState =
-        MutableStateFlow<UiState>(UiState.CredentialScreen)
-    val uiState: StateFlow<UiState> = _uiState
-
-    private lateinit var requestGet: Request.Get
-    private lateinit var entryInfo: CredentialEntryInfo
-
-    @MainThread
-    fun initialize(entry: CredentialEntryInfo) {
-        this.entryInfo = entry
-    }
-
-    fun onDismissClick() {
-        _uiState.value = UiState.Cancel
-    }
-
-    fun onContinueClick() {
-        _uiState.value = UiState.CredentialSelected(
-            intentSenderRequest = entryInfo.getIntentSenderRequest()
-        )
-    }
-
-    fun onSignInOptionsClick() {
-        // TODO(b/322797032) Implement navigation route for single credential screen to multiple
-        // credentials
-    }
-
-    fun onInfoRetrieved(
-        resultCode: Int? = null,
-        resultData: Intent? = null,
-    ) {
-        val userSelectionDialogResult = UserSelectionDialogResult(
-            requestGet.token,
-            entryInfo.providerId,
-            entryInfo.entryKey,
-            entryInfo.entrySubkey,
-            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
-        )
-        credentialManagerClient.sendResult(userSelectionDialogResult)
-    }
-}
-
diff --git a/packages/EasterEgg/res/values-night/styles.xml b/packages/EasterEgg/res/values-night/styles.xml
index 4edf692..6ea2eae 100644
--- a/packages/EasterEgg/res/values-night/styles.xml
+++ b/packages/EasterEgg/res/values-night/styles.xml
@@ -15,7 +15,7 @@
 -->
 <resources>
     <style name="AppTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
-        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+        <item name="android:windowLayoutInDisplayCutoutMode">always</item>
         <item name="android:windowLightNavigationBar">false</item>
     </style>
 </resources>
diff --git a/packages/EasterEgg/res/values/styles.xml b/packages/EasterEgg/res/values/styles.xml
index e576526..4a2cb48f6 100644
--- a/packages/EasterEgg/res/values/styles.xml
+++ b/packages/EasterEgg/res/values/styles.xml
@@ -16,7 +16,7 @@
 <resources>
 
     <style name="AppTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen">
-        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+        <item name="android:windowLayoutInDisplayCutoutMode">always</item>
         <item name="android:windowLightNavigationBar">true</item>
     </style>
 
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt
index 621a8d7..9d3fb66 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt
@@ -62,7 +62,7 @@
             }
         Log.i(LOG_TAG, "[$name] Restore ${data.size()} bytes for $key to $file")
         val inputStream = LimitedNoCloseInputStream(data)
-        checksum.reset()
+        val checksum = createChecksum()
         val checkedInputStream = CheckedInputStream(inputStream, checksum)
         try {
             val codec = BackupCodec.fromId(checkedInputStream.read().toByte())
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
index ea2fb72..c4c00cb 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
@@ -36,6 +36,7 @@
 import java.util.zip.CRC32
 import java.util.zip.CheckedInputStream
 import java.util.zip.CheckedOutputStream
+import java.util.zip.Checksum
 
 internal const val LOG_TAG = "BackupRestoreStorage"
 
@@ -54,15 +55,6 @@
      */
     abstract val name: String
 
-    private val entities: List<BackupRestoreEntity> by lazy { createBackupRestoreEntities() }
-
-    /**
-     * Checksum of the data.
-     *
-     * Always call [java.util.zip.Checksum.reset] before using it.
-     */
-    protected val checksum = CRC32()
-
     /**
      * Entity states represented by checksum.
      *
@@ -70,13 +62,16 @@
      */
     protected val entityStates = MutableScatterMap<String, Long>()
 
+    /** Entities created by [createBackupRestoreEntities]. This field is for restore only. */
+    private var entities: List<BackupRestoreEntity>? = null
+
     /** Entities to back up and restore. */
     abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>
 
     /** Default codec used to encode/decode the entity data. */
     open fun defaultCodec(): BackupCodec = BackupZipCodec.BEST_COMPRESSION
 
-    override fun performBackup(
+    final override fun performBackup(
         oldState: ParcelFileDescriptor?,
         data: BackupDataOutput,
         newState: ParcelFileDescriptor,
@@ -88,6 +83,9 @@
             return
         }
         Log.i(LOG_TAG, "[$name] Backup start")
+        val checksum = createChecksum()
+        // recreate entities for backup to avoid stale states
+        val entities = createBackupRestoreEntities()
         for (entity in entities) {
             val key = entity.key
             val outputStream = ByteArrayOutputStream()
@@ -103,7 +101,8 @@
                 }
             when (result) {
                 EntityBackupResult.UPDATE -> {
-                    if (updateEntityState(key)) {
+                    val value = checksum.value
+                    if (entityStates.put(key, value) != value) {
                         val payload = outputStream.toByteArray()
                         val size = payload.size
                         data.writeEntityHeader(key, size)
@@ -126,15 +125,10 @@
                 }
             }
         }
-        newState.writeEntityStates(entityStates)
+        newState.writeAndClearEntityStates()
         Log.i(LOG_TAG, "[$name] Backup end")
     }
 
-    private fun updateEntityState(key: String): Boolean {
-        val value = checksum.value
-        return entityStates.put(key, value) != value
-    }
-
     /** Returns if backup is enabled. */
     open fun enableBackup(backupContext: BackupContext): Boolean = true
 
@@ -144,13 +138,14 @@
         return codec.encode(outputStream)
     }
 
+    /** This callback is invoked for every backed up entity. */
     override fun restoreEntity(data: BackupDataInputStream) {
         val key = data.key
         if (!enableRestore()) {
             Log.i(LOG_TAG, "[$name] Restore disabled, ignore entity $key")
             return
         }
-        val entity = entities.firstOrNull { it.key == key }
+        val entity = ensureEntities().firstOrNull { it.key == key }
         if (entity == null) {
             Log.w(LOG_TAG, "[$name] Cannot find handler for entity $key")
             return
@@ -159,7 +154,7 @@
         val restoreContext = RestoreContext(key)
         val codec = entity.codec() ?: defaultCodec()
         val inputStream = LimitedNoCloseInputStream(data)
-        checksum.reset()
+        val checksum = createChecksum()
         val checkedInputStream = CheckedInputStream(inputStream, checksum)
         try {
             entity.restore(restoreContext, wrapRestoreInputStream(codec, checkedInputStream))
@@ -169,6 +164,9 @@
         }
     }
 
+    private fun ensureEntities(): List<BackupRestoreEntity> =
+        entities ?: createBackupRestoreEntities().also { entities = it }
+
     /** Returns if restore is enabled. */
     open fun enableRestore(): Boolean = true
 
@@ -185,7 +183,8 @@
     }
 
     final override fun writeNewStateDescription(newState: ParcelFileDescriptor) {
-        newState.writeEntityStates(entityStates)
+        entities = null // clear to reduce memory footprint
+        newState.writeAndClearEntityStates()
         onRestoreFinished()
     }
 
@@ -223,24 +222,29 @@
         }
     }
 
-    private fun ParcelFileDescriptor.writeEntityStates(state: MutableScatterMap<String, Long>) {
+    private fun ParcelFileDescriptor.writeAndClearEntityStates() {
         // do not close the streams
         val fileOutputStream = FileOutputStream(fileDescriptor)
         val dataOutputStream = DataOutputStream(fileOutputStream)
         try {
             dataOutputStream.writeByte(STATE_VERSION.toInt())
-            dataOutputStream.writeInt(state.size)
-            state.forEach { key, value ->
+            dataOutputStream.writeInt(entityStates.size)
+            entityStates.forEach { key, value ->
                 dataOutputStream.writeUTF(key)
                 dataOutputStream.writeLong(value)
             }
         } catch (exception: Exception) {
             Log.e(LOG_TAG, "[$name] Fail to write state file", exception)
         }
+        entityStates.clear()
+        entityStates.trim() // trim to reduce memory footprint
     }
 
     companion object {
         private const val STATE_VERSION: Byte = 0
+
+        /** Checksum for entity backup data. */
+        fun createChecksum(): Checksum = CRC32()
     }
 }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
index 9866023..3f74ed5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
@@ -36,7 +36,7 @@
         TwoTargetPreference(
             title = title,
             summary = summary,
-            onClick = onClick,
+            primaryOnClick = onClick,
             icon = icon,
         ) {
             IconButton(onClick = onButtonClick) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
index 3216e37..3f68804 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
@@ -34,7 +34,8 @@
 internal fun TwoTargetPreference(
     title: String,
     summary: () -> String,
-    onClick: () -> Unit,
+    primaryEnabled: () -> Boolean = { true },
+    primaryOnClick: (() -> Unit)?,
     icon: @Composable (() -> Unit)? = null,
     widget: @Composable () -> Unit,
 ) {
@@ -50,7 +51,8 @@
                     override val title = title
                     override val summary = summary
                     override val icon = icon
-                    override val onClick = onClick
+                    override val enabled = primaryEnabled
+                    override val onClick = primaryOnClick
                 }
             )
         }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
index 7eed745..8b546b4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
@@ -24,13 +24,15 @@
 fun TwoTargetSwitchPreference(
     model: SwitchPreferenceModel,
     icon: @Composable (() -> Unit)? = null,
-    onClick: () -> Unit,
+    primaryEnabled: () -> Boolean = { true },
+    primaryOnClick: (() -> Unit)?,
 ) {
     EntryHighlight {
         TwoTargetPreference(
             title = model.title,
             summary = model.summary,
-            onClick = onClick,
+            primaryEnabled = primaryEnabled,
+            primaryOnClick = primaryOnClick,
             icon = icon,
         ) {
             SettingsSwitch(
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt
index 3455851..0acf287 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotEnabled
 import androidx.compose.ui.test.assertIsOff
 import androidx.compose.ui.test.assertIsOn
 import androidx.compose.ui.test.isToggleable
@@ -46,7 +47,7 @@
             TestTwoTargetSwitchPreference(changeable = true)
         }
 
-        composeTestRule.onNodeWithText("TwoTargetSwitchPreference").assertIsDisplayed()
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
     }
 
     @Test
@@ -79,7 +80,7 @@
     }
 
     @Test
-    fun clickable_canBeClick() {
+    fun clickable_primaryEnabled_canBeClick() {
         var clicked = false
         composeTestRule.setContent {
             TestTwoTargetSwitchPreference(changeable = false) {
@@ -87,26 +88,54 @@
             }
         }
 
-        composeTestRule.onNodeWithText("TwoTargetSwitchPreference").performClick()
+        composeTestRule.onNodeWithText(TITLE).performClick()
         assertThat(clicked).isTrue()
     }
-}
 
-@Composable
-private fun TestTwoTargetSwitchPreference(
-    changeable: Boolean,
-    onClick: () -> Unit = {},
-) {
-    var checked by rememberSaveable { mutableStateOf(false) }
-    TwoTargetSwitchPreference(
-        model = remember {
-            object : SwitchPreferenceModel {
-                override val title = "TwoTargetSwitchPreference"
-                override val checked = { checked }
-                override val changeable = { changeable }
-                override val onCheckedChange = { newChecked: Boolean -> checked = newChecked }
+    @Test
+    fun clickable_primaryNotEnabled_assertIsNotEnabled() {
+        composeTestRule.setContent {
+            TestTwoTargetSwitchPreference(changeable = false, primaryEnabled = false)
+        }
+
+        composeTestRule.onNodeWithText(TITLE).assertIsNotEnabled()
+    }
+
+    @Test
+    fun clickable_primaryNotEnabled_canNotBeClick() {
+        var clicked = false
+        composeTestRule.setContent {
+            TestTwoTargetSwitchPreference(changeable = false, primaryEnabled = false) {
+                clicked = true
             }
-        },
-        onClick = onClick,
-    )
+        }
+
+        composeTestRule.onNodeWithText(TITLE).performClick()
+        assertThat(clicked).isFalse()
+    }
+
+    @Composable
+    private fun TestTwoTargetSwitchPreference(
+        changeable: Boolean,
+        primaryEnabled: Boolean = true,
+        primaryOnClick: () -> Unit = {},
+    ) {
+        var checked by rememberSaveable { mutableStateOf(false) }
+        TwoTargetSwitchPreference(
+            model = remember {
+                object : SwitchPreferenceModel {
+                    override val title = TITLE
+                    override val checked = { checked }
+                    override val changeable = { changeable }
+                    override val onCheckedChange = { newChecked: Boolean -> checked = newChecked }
+                }
+            },
+            primaryEnabled = { primaryEnabled },
+            primaryOnClick = primaryOnClick,
+        )
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+    }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
index bea14c3..5c2d770 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
@@ -38,6 +38,6 @@
             override val onCheckedChange = onCheckedChange
         },
         icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
-        onClick = onClick,
+        primaryOnClick = onClick,
     )
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
index ac85dd4..389b3c1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
@@ -54,14 +54,27 @@
         return
     }
     val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
-    val restrictedSwitchModel = remember(restrictedMode) {
+    val restrictedModel = remember(restrictedMode) {
         RestrictedPreferenceModel(model, restrictedMode)
     }
-    restrictedSwitchModel.RestrictionWrapper {
-        Preference(restrictedSwitchModel)
+    restrictedModel.RestrictionWrapper {
+        Preference(restrictedModel)
     }
 }
 
+internal fun RestrictedMode?.restrictEnabled(enabled: () -> Boolean) = when (this) {
+    NoRestricted -> enabled
+    else -> ({ false })
+}
+
+internal fun <T> RestrictedMode?.restrictOnClick(onClick: T): T? = when (this) {
+    NoRestricted -> onClick
+    // Need to passthrough onClick for clickable semantics, although since enabled is false so
+    // this will not be called.
+    BaseUserRestricted -> onClick
+    else -> null
+}
+
 private class RestrictedPreferenceModel(
     model: PreferenceModel,
     private val restrictedMode: RestrictedMode?,
@@ -69,19 +82,8 @@
     override val title = model.title
     override val summary = model.summary
     override val icon = model.icon
-
-    override val enabled = when (restrictedMode) {
-        NoRestricted -> model.enabled
-        else -> ({ false })
-    }
-
-    override val onClick = when (restrictedMode) {
-        NoRestricted -> model.onClick
-        // Need to passthrough onClick for clickable semantics, although since enabled is false so
-        // this will not be called.
-        BaseUserRestricted -> model.onClick
-        else -> null
-    }
+    override val enabled = restrictedMode.restrictEnabled(model.enabled)
+    override val onClick = restrictedMode.restrictOnClick(model.onClick)
 
     @Composable
     fun RestrictionWrapper(content: @Composable () -> Unit) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
index aba3460..5dfecb0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -60,18 +60,9 @@
         is BlockedByEcm -> model.checked
     }
 
-    override val changeable = if (restrictedMode is NoRestricted) model.changeable else ({ false })
+    override val changeable = restrictedMode.restrictEnabled(model.changeable)
 
-    override val onCheckedChange = when (restrictedMode) {
-        null -> null
-        is NoRestricted -> model.onCheckedChange
-        // Need to passthrough onCheckedChange for toggleable semantics, although since changeable
-        // is false so this will not be called.
-        is BaseUserRestricted -> model.onCheckedChange
-        // Pass null since semantics ToggleableState is provided in RestrictionWrapper.
-        is BlockedByAdmin -> null
-        is BlockedByEcm -> null
-    }
+    override val onCheckedChange = restrictedMode.restrictOnClick(model.onCheckedChange)
 
     @Composable
     fun RestrictionWrapper(content: @Composable () -> Unit) {
@@ -116,13 +107,12 @@
 
     companion object {
         @Composable
-        fun RestrictionsProviderFactory.RestrictedSwitchWrapper(
+        fun RestrictedSwitchWrapper(
             model: SwitchPreferenceModel,
-            restrictions: Restrictions,
+            restrictedMode: RestrictedMode?,
             content: @Composable (SwitchPreferenceModel) -> Unit,
         ) {
             val context = LocalContext.current
-            val restrictedMode = rememberRestrictedMode(restrictions).value
             val restrictedSwitchPreferenceModel = remember(restrictedMode) {
                 RestrictedSwitchPreferenceModel(context, model, restrictedMode)
             }
@@ -131,6 +121,15 @@
             }
         }
 
+        @Composable
+        fun RestrictionsProviderFactory.RestrictedSwitchWrapper(
+            model: SwitchPreferenceModel,
+            restrictions: Restrictions,
+            content: @Composable (SwitchPreferenceModel) -> Unit,
+        ) {
+            RestrictedSwitchWrapper(model, rememberRestrictedMode(restrictions).value, content)
+        }
+
         fun getSummary(
             context: Context,
             restrictedModeSupplier: () -> RestrictedMode?,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
new file mode 100644
index 0000000..e100773
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
+import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreferenceModel.Companion.RestrictedSwitchWrapper
+
+@Composable
+fun RestrictedTwoTargetSwitchPreference(
+    model: SwitchPreferenceModel,
+    icon: @Composable (() -> Unit)? = null,
+    restrictions: Restrictions,
+    primaryEnabled: () -> Boolean = { true },
+    primaryOnClick: (() -> Unit)?,
+) {
+    RestrictedTwoTargetSwitchPreference(
+        model = model,
+        icon = icon,
+        primaryEnabled = primaryEnabled,
+        primaryOnClick = primaryOnClick,
+        restrictions = restrictions,
+        restrictionsProviderFactory = ::RestrictionsProviderImpl,
+    )
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedTwoTargetSwitchPreference(
+    model: SwitchPreferenceModel,
+    icon: @Composable (() -> Unit)? = null,
+    primaryEnabled: () -> Boolean = { true },
+    primaryOnClick: (() -> Unit)?,
+    restrictions: Restrictions,
+    restrictionsProviderFactory: RestrictionsProviderFactory,
+) {
+    if (restrictions.isEmpty()) {
+        TwoTargetSwitchPreference(model, icon, primaryEnabled, primaryOnClick)
+        return
+    }
+    val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
+    RestrictedSwitchWrapper(model, restrictedMode) { restrictedModel ->
+        TwoTargetSwitchPreference(
+            model = restrictedModel,
+            icon = icon,
+            primaryEnabled = restrictedMode.restrictEnabled(primaryEnabled),
+            primaryOnClick = restrictedMode.restrictOnClick(primaryOnClick),
+        )
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt
new file mode 100644
index 0000000..bdff89f
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.isOff
+import androidx.compose.ui.test.isOn
+import androidx.compose.ui.test.isToggleable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByEcm
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RestrictedTwoTargetSwitchPreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+    private val fakeBlockedByEcm = FakeBlockedByEcm()
+
+    private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+    private val switchPreferenceModel = object : SwitchPreferenceModel {
+        override val title = TITLE
+        private val checkedState = mutableStateOf(true)
+        override val checked = { checkedState.value }
+        override val onCheckedChange: (Boolean) -> Unit = { checkedState.value = it }
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNode(isOn()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_toggleable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+        composeTestRule.onNode(isToggleable()).performClick()
+
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenNoRestricted_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNode(isOn()).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenNoRestricted_toggleable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+        composeTestRule.onNode(isToggleable()).performClick()
+
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled()
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_notToggleable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+        composeTestRule.onNode(isToggleable()).performClick()
+
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNodeWithText(FakeBlockedByAdmin.SUMMARY).assertIsDisplayed()
+        composeTestRule.onNode(isOn()).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_clickPrimary() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+        composeTestRule.onNodeWithText(TITLE).performClick()
+
+        assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_clickSwitch() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+        composeTestRule.onNode(isToggleable()).performClick()
+
+        assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+    }
+
+    @Test
+    fun whenBlockedByEcm_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNodeWithText(FakeBlockedByEcm.SUMMARY).assertIsDisplayed()
+        composeTestRule.onNode(isOn()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBlockedByEcm_click() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(fakeBlockedByEcm.showRestrictedSettingsDetailsIsCalled).isTrue()
+    }
+
+    private fun setContent(restrictions: Restrictions, primaryOnClick: (() -> Unit)? = {}) {
+        composeTestRule.setContent {
+            RestrictedTwoTargetSwitchPreference(
+                model = switchPreferenceModel,
+                primaryOnClick = primaryOnClick,
+                restrictions = restrictions,
+            ) { _, _ ->
+                fakeRestrictionsProvider
+            }
+        }
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val USER_ID = 0
+        const val RESTRICTION_KEY = "restriction_key"
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index 840c936..b7108c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -236,7 +236,8 @@
             // Handle specific carrier config values for the default data SIM
             int defaultDataSubId = SubscriptionManager.from(context)
                     .getDefaultDataSubscriptionId();
-            PersistableBundle b = configMgr.getConfigForSubId(defaultDataSubId);
+            PersistableBundle b = configMgr == null ? null
+                        : configMgr.getConfigForSubId(defaultDataSubId);
             if (b != null) {
                 config.alwaysShowDataRatIcon = b.getBoolean(
                         CarrierConfigManager.KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 2a8eb9b..3266c12 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -707,23 +707,36 @@
         // Update/add new keys
         for (String key : keyValues.keySet()) {
             String value = keyValues.get(key);
+
+            // Rename key if it's an aconfig flag.
+            String flagName = key;
+            if (Flags.stageAllAconfigFlags() && isConfigSettingsKey(mKey)) {
+                int slashIndex = flagName.indexOf("/");
+                boolean stageFlag = slashIndex > 0 && slashIndex != flagName.length();
+                boolean isAconfig = trunkFlagMap != null && trunkFlagMap.containsKey(flagName);
+                if (stageFlag && isAconfig) {
+                    String flagWithoutNamespace = flagName.substring(slashIndex + 1);
+                    flagName = "staged/" + namespace + "*" + flagWithoutNamespace;
+                }
+            }
+
             String oldValue = null;
-            Setting state = mSettings.get(key);
+            Setting state = mSettings.get(flagName);
             if (state == null) {
-                state = new Setting(key, value, false, packageName, null);
-                mSettings.put(key, state);
-                changedKeys.add(key); // key was added
+                state = new Setting(flagName, value, false, packageName, null);
+                mSettings.put(flagName, state);
+                changedKeys.add(flagName); // key was added
             } else if (state.value != value) {
                 oldValue = state.value;
                 state.update(value, false, packageName, null, true,
                         /* overrideableByRestore */ false);
-                changedKeys.add(key); // key was updated
+                changedKeys.add(flagName); // key was updated
             } else {
                 // this key/value already exists, no change and no logging necessary
                 continue;
             }
 
-            FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, key, value, state.value,
+            FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, flagName, value, state.value,
                     oldValue, /* tag */ null, /* make default */ false,
                     getUserIdFromKey(mKey), FrameworkStatsLog.SETTING_CHANGED__REASON__UPDATED);
             addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, state);
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 33362a2..e0e31d7 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -740,6 +740,51 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_STAGE_ALL_ACONFIG_FLAGS)
+    public void testSetSettingsLockedStagesAconfigFlags() throws Exception {
+        int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+
+        SettingsState settingsState = new SettingsState(
+                InstrumentationRegistry.getContext(), mLock, mSettingsFile, configKey,
+                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
+        String prefix = "test_namespace";
+        String packageName = "com.android.flags";
+        Map<String, String> keyValues =
+                Map.of("test_namespace/com.android.flags.flag3", "true");
+
+        parsed_flags flags = parsed_flags
+                .newBuilder()
+                .addParsedFlag(parsed_flag
+                    .newBuilder()
+                        .setPackage(packageName)
+                        .setName("flag3")
+                        .setNamespace(prefix)
+                        .setDescription("test flag")
+                        .addBug("12345678")
+                        .setState(Aconfig.flag_state.DISABLED)
+                        .setPermission(Aconfig.flag_permission.READ_WRITE))
+                .build();
+
+        synchronized (mLock) {
+            settingsState.loadAconfigDefaultValues(
+                    flags.toByteArray(), settingsState.getAconfigDefaultValues());
+            List<String> updates =
+                    settingsState.setSettingsLocked("test_namespace/", keyValues, packageName);
+            assertEquals(1, updates.size());
+            assertEquals(updates.get(0), "staged/test_namespace*com.android.flags.flag3");
+
+            SettingsState.Setting s;
+
+            s = settingsState.getSettingLocked("test_namespace/com.android.flags.flag3");
+            assertNull(s.getValue());
+
+            s = settingsState.getSettingLocked("staged/test_namespace*com.android.flags.flag3");
+            assertEquals("true", s.getValue());
+        }
+    }
+
+    @Test
     public void testsetSettingsLockedKeepTrunkDefault() throws Exception {
         final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile));
         os.print(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 9ee69bc..d0c4984 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -24,10 +24,10 @@
 import com.android.compose.animation.scene.updateSceneTransitionLayoutState
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.ui.compose.extensions.allowGestures
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 
 object Communal {
     object Elements {
@@ -63,6 +63,7 @@
 fun CommunalContainer(
     modifier: Modifier = Modifier,
     viewModel: CommunalViewModel,
+    dialogFactory: SystemUIDialogFactory,
 ) {
     val currentScene: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
     val sceneTransitionLayoutState =
@@ -82,7 +83,7 @@
 
     SceneTransitionLayout(
         state = sceneTransitionLayoutState,
-        modifier = modifier.fillMaxSize().allowGestures(allowed = touchesAllowed),
+        modifier = modifier.fillMaxSize(),
         swipeSourceDetector =
             FixedSizeEdgeDetector(
                 dimensionResource(id = R.dimen.communal_gesture_initiation_width)
@@ -91,9 +92,14 @@
         scene(
             CommunalScenes.Blank,
             userActions =
-                mapOf(
-                    Swipe(SwipeDirection.Left, fromSource = Edge.Right) to CommunalScenes.Communal
-                )
+                if (touchesAllowed) {
+                    mapOf(
+                        Swipe(SwipeDirection.Left, fromSource = Edge.Right) to
+                            CommunalScenes.Communal
+                    )
+                } else {
+                    emptyMap()
+                }
         ) {
             // This scene shows nothing only allowing for transitions to the communal scene.
             Box(modifier = Modifier.fillMaxSize())
@@ -102,9 +108,15 @@
         scene(
             CommunalScenes.Communal,
             userActions =
-                mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank),
+                if (touchesAllowed) {
+                    mapOf(
+                        Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank
+                    )
+                } else {
+                    emptyMap()
+                },
         ) {
-            CommunalScene(viewModel, modifier = modifier)
+            CommunalScene(viewModel, dialogFactory, modifier = modifier)
         }
     }
 }
@@ -113,6 +125,7 @@
 @Composable
 private fun SceneScope.CommunalScene(
     viewModel: BaseCommunalViewModel,
+    dialogFactory: SystemUIDialogFactory,
     modifier: Modifier = Modifier,
 ) {
     Box(
@@ -121,5 +134,7 @@
                 .fillMaxSize()
                 .background(LocalAndroidColorScheme.current.outlineVariant),
     )
-    Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
+    Box(modifier.element(Communal.Elements.Content)) {
+        CommunalHub(viewModel = viewModel, dialogFactory = dialogFactory)
+    }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 3050f7a..0d6b710 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -29,6 +29,7 @@
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
@@ -118,12 +119,13 @@
 import com.android.systemui.communal.ui.compose.extensions.allowGestures
 import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
 import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
-import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
+import com.android.systemui.communal.ui.compose.extensions.observeTaps
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalComposeUiApi::class)
@@ -131,6 +133,7 @@
 fun CommunalHub(
     modifier: Modifier = Modifier,
     viewModel: BaseCommunalViewModel,
+    dialogFactory: SystemUIDialogFactory? = null,
     widgetConfigurator: WidgetConfigurator? = null,
     onOpenWidgetPicker: (() -> Unit)? = null,
     onEditDone: (() -> Unit)? = null,
@@ -165,7 +168,7 @@
                 .pointerInput(gridState, contentOffset, contentListState) {
                     // If not in edit mode, don't allow selecting items.
                     if (!viewModel.isEditMode) return@pointerInput
-                    observeTapsWithoutConsuming { offset ->
+                    observeTaps { offset ->
                         val adjustedOffset = offset - contentOffset
                         val index = firstIndexAtOffset(gridState, adjustedOffset)
                         val key = index?.let { keyAtIndexIfEditable(contentListState.list, index) }
@@ -271,6 +274,31 @@
             )
         }
 
+        if (viewModel is CommunalViewModel && dialogFactory != null) {
+            val isEnableWidgetDialogShowing by
+                viewModel.isEnableWidgetDialogShowing.collectAsState(false)
+            val isEnableWorkProfileDialogShowing by
+                viewModel.isEnableWorkProfileDialogShowing.collectAsState(false)
+
+            EnableWidgetDialog(
+                isEnableWidgetDialogVisible = isEnableWidgetDialogShowing,
+                dialogFactory = dialogFactory,
+                title = stringResource(id = R.string.dialog_title_to_allow_any_widget),
+                positiveButtonText = stringResource(id = R.string.button_text_to_open_settings),
+                onConfirm = viewModel::onEnableWidgetDialogConfirm,
+                onCancel = viewModel::onEnableWidgetDialogCancel
+            )
+
+            EnableWidgetDialog(
+                isEnableWidgetDialogVisible = isEnableWorkProfileDialogShowing,
+                dialogFactory = dialogFactory,
+                title = stringResource(id = R.string.work_mode_off_title),
+                positiveButtonText = stringResource(id = R.string.work_mode_turn_on),
+                onConfirm = viewModel::onEnableWorkProfileDialogConfirm,
+                onCancel = viewModel::onEnableWorkProfileDialogCancel
+            )
+        }
+
         // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving
         // touches, so that the SceneTransitionLayout can intercept the touches and allow an edge
         // swipe back to the blank scene.
@@ -616,7 +644,7 @@
             WidgetContent(viewModel, model, size, selected, widgetConfigurator, modifier)
         is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier)
         is CommunalContentModel.WidgetContent.DisabledWidget ->
-            DisabledWidgetPlaceholder(model, modifier)
+            DisabledWidgetPlaceholder(model, viewModel, modifier)
         is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier)
         is CommunalContentModel.CtaTileInEditMode ->
             CtaTileInEditModeContent(modifier, onOpenWidgetPicker)
@@ -754,7 +782,16 @@
     modifier: Modifier = Modifier,
 ) {
     Box(
-        modifier = modifier,
+        modifier =
+            modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) {
+                Modifier.pointerInput(Unit) {
+                    // consume tap to prevent the child view from triggering interactions with the
+                    // app widget
+                    observeTaps(shouldConsume = true) { _ ->
+                        viewModel.onOpenEnableWorkProfileDialog()
+                    }
+                }
+            }
     ) {
         AndroidView(
             modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
@@ -826,6 +863,7 @@
 @Composable
 fun DisabledWidgetPlaceholder(
     model: CommunalContentModel.WidgetContent.DisabledWidget,
+    viewModel: BaseCommunalViewModel,
     modifier: Modifier = Modifier,
 ) {
     val context = LocalContext.current
@@ -839,10 +877,17 @@
 
     Column(
         modifier =
-            modifier.background(
-                MaterialTheme.colorScheme.surfaceVariant,
-                RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
-            ),
+            modifier
+                .background(
+                    MaterialTheme.colorScheme.surfaceVariant,
+                    RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
+                )
+                .clickable(
+                    enabled = !viewModel.isEditMode,
+                    interactionSource = null,
+                    indication = null,
+                    onClick = viewModel::onOpenEnableWidgetDialog
+                ),
         verticalArrangement = Arrangement.Center,
         horizontalAlignment = Alignment.CenterHorizontally,
     ) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index 3d88ad5..9e905ac 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -38,6 +39,7 @@
 @Inject
 constructor(
     private val viewModel: CommunalViewModel,
+    private val dialogFactory: SystemUIDialogFactory,
 ) : ComposableScene {
     override val key = Scenes.Communal
 
@@ -51,6 +53,6 @@
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        CommunalHub(modifier, viewModel)
+        CommunalHub(modifier, viewModel, dialogFactory)
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt
new file mode 100644
index 0000000..df11206
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.statusbar.phone.create
+
+/** Dialog shown upon tapping a disabled widget which allows users to enable the widget. */
+@Composable
+fun EnableWidgetDialog(
+    isEnableWidgetDialogVisible: Boolean,
+    dialogFactory: SystemUIDialogFactory,
+    title: String,
+    positiveButtonText: String,
+    onConfirm: () -> Unit,
+    onCancel: () -> Unit
+) {
+    var dialog: ComponentSystemUIDialog? by remember { mutableStateOf(null) }
+    val context = LocalView.current.context
+
+    DisposableEffect(isEnableWidgetDialogVisible) {
+        if (isEnableWidgetDialogVisible) {
+            dialog =
+                dialogFactory.create(
+                    context = context,
+                ) {
+                    DialogComposable(title, positiveButtonText, onConfirm, onCancel)
+                }
+            dialog?.apply {
+                setCancelable(true)
+                setCanceledOnTouchOutside(true)
+                setOnCancelListener { onCancel() }
+                show()
+            }
+        }
+
+        onDispose {
+            dialog?.dismiss()
+            dialog = null
+        }
+    }
+}
+
+@Composable
+private fun DialogComposable(
+    title: String,
+    positiveButtonText: String,
+    onConfirm: () -> Unit,
+    onCancel: () -> Unit,
+) {
+    Box(
+        Modifier.fillMaxWidth()
+            .padding(top = 18.dp, bottom = 8.dp)
+            .background(LocalAndroidColorScheme.current.surfaceBright, RoundedCornerShape(28.dp))
+    ) {
+        Column(
+            modifier = Modifier.fillMaxWidth(),
+            verticalArrangement = Arrangement.spacedBy(20.dp),
+        ) {
+            Box(
+                modifier = Modifier.padding(horizontal = 24.dp).fillMaxWidth().wrapContentHeight(),
+                contentAlignment = Alignment.TopStart
+            ) {
+                Text(
+                    text = title,
+                    style = MaterialTheme.typography.titleMedium,
+                    color = LocalAndroidColorScheme.current.onSurface,
+                    textAlign = TextAlign.Center,
+                    maxLines = 1,
+                )
+            }
+
+            Box(
+                modifier = Modifier.padding(end = 12.dp).fillMaxWidth().wrapContentHeight(),
+                contentAlignment = Alignment.Center
+            ) {
+                Row(
+                    modifier = Modifier.fillMaxWidth(),
+                    horizontalArrangement = Arrangement.End,
+                ) {
+                    TextButton(
+                        contentPadding = PaddingValues(16.dp),
+                        onClick = onCancel,
+                    ) {
+                        Text(
+                            text = stringResource(R.string.cancel),
+                        )
+                    }
+                    TextButton(
+                        contentPadding = PaddingValues(16.dp),
+                        onClick = onConfirm,
+                    ) {
+                        Text(
+                            text = positiveButtonText,
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
index bc1e429..379c165 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
@@ -30,16 +30,18 @@
 import kotlinx.coroutines.coroutineScope
 
 /**
- * Observe taps without actually consuming them, so child elements can still respond to them. Long
+ * Observe taps without consuming them by default, so child elements can still respond to them. Long
  * presses are excluded.
  */
-suspend fun PointerInputScope.observeTapsWithoutConsuming(
+suspend fun PointerInputScope.observeTaps(
     pass: PointerEventPass = PointerEventPass.Initial,
+    shouldConsume: Boolean = false,
     onTap: ((Offset) -> Unit)? = null,
 ) = coroutineScope {
     if (onTap == null) return@coroutineScope
     awaitEachGesture {
-        awaitFirstDown(pass = pass)
+        val down = awaitFirstDown(pass = pass)
+        if (shouldConsume) down.consume()
         val tapTimeout = viewConfiguration.longPressTimeoutMillis
         val up = withTimeoutOrNull(tapTimeout) { waitForUpOrCancellation(pass = pass) }
         if (up != null) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index b5499b7..bc4e555 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -18,13 +18,16 @@
 
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalView
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.animation.scene.transitions
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import javax.inject.Inject
@@ -40,6 +43,7 @@
 constructor(
     private val viewModel: LockscreenContentViewModel,
     private val blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
+    private val clockInteractor: KeyguardClockInteractor,
 ) {
 
     private val sceneKeyByBlueprint: Map<ComposableLockscreenSceneBlueprint, SceneKey> by lazy {
@@ -55,6 +59,12 @@
     ) {
         val coroutineScope = rememberCoroutineScope()
         val blueprintId by viewModel.blueprintId(coroutineScope).collectAsState()
+        val view = LocalView.current
+        DisposableEffect(view) {
+            clockInteractor.clockEventController.registerListeners(view)
+
+            onDispose { clockInteractor.clockEventController.unregisterListeners() }
+        }
 
         // Switch smoothly between blueprints, any composable tagged with element() will be
         // transition-animated between any two blueprints, if they both display the same element.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
new file mode 100644
index 0000000..c5ff859
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.blueprint
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smartspaceElementKey
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceInTransition.Companion.CLOCK_IN_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceInTransition.Companion.CLOCK_IN_START_DELAY_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceOutTransition.Companion.CLOCK_OUT_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.SmartspaceMoveTransition.Companion.STATUS_AREA_MOVE_DOWN_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.SmartspaceMoveTransition.Companion.STATUS_AREA_MOVE_UP_MILLIS
+
+object ClockTransition {
+    val defaultClockTransitions = transitions {
+        from(ClockScenes.smallClockScene, to = ClockScenes.largeClockScene) {
+            transitioningToLargeClock()
+        }
+        from(ClockScenes.largeClockScene, to = ClockScenes.smallClockScene) {
+            transitioningToSmallClock()
+        }
+    }
+
+    private fun TransitionBuilder.transitioningToLargeClock() {
+        spec = tween(durationMillis = STATUS_AREA_MOVE_UP_MILLIS.toInt())
+        timestampRange(
+            startMillis = CLOCK_IN_START_DELAY_MILLIS.toInt(),
+            endMillis = (CLOCK_IN_START_DELAY_MILLIS + CLOCK_IN_MILLIS).toInt()
+        ) {
+            fade(largeClockElementKey)
+        }
+
+        timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) { fade(smallClockElementKey) }
+        anchoredTranslate(smallClockElementKey, smartspaceElementKey)
+    }
+
+    private fun TransitionBuilder.transitioningToSmallClock() {
+        spec = tween(durationMillis = STATUS_AREA_MOVE_DOWN_MILLIS.toInt())
+        timestampRange(
+            startMillis = CLOCK_IN_START_DELAY_MILLIS.toInt(),
+            endMillis = (CLOCK_IN_START_DELAY_MILLIS + CLOCK_IN_MILLIS).toInt()
+        ) {
+            fade(smallClockElementKey)
+        }
+
+        timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) { fade(largeClockElementKey) }
+        anchoredTranslate(smallClockElementKey, smartspaceElementKey)
+    }
+}
+
+object ClockScenes {
+    val smallClockScene = SceneKey("small-clock-scene")
+    val largeClockScene = SceneKey("large-clock-scene")
+}
+
+object ClockElementKeys {
+    val largeClockElementKey = ElementKey("large-clock")
+    val smallClockElementKey = ElementKey("small-clock")
+    val smartspaceElementKey = ElementKey("smart-space")
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index d23cd0c..9509fd2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -17,19 +17,14 @@
 package com.android.systemui.keyguard.ui.composable.blueprint
 
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.padding
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -38,11 +33,8 @@
 import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
 import com.android.systemui.keyguard.ui.composable.section.NotificationSection
 import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.media.controls.ui.composable.MediaCarousel
-import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
@@ -59,14 +51,12 @@
     private val viewModel: LockscreenContentViewModel,
     private val statusBarSection: StatusBarSection,
     private val clockSection: DefaultClockSection,
-    private val smartSpaceSection: SmartSpaceSection,
     private val notificationSection: NotificationSection,
     private val lockSection: LockSection,
     private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
     private val bottomAreaSection: BottomAreaSection,
     private val settingsMenuSection: SettingsMenuSection,
     private val mediaCarouselSection: MediaCarouselSection,
-    private val clockInteractor: KeyguardClockInteractor,
 ) : ComposableLockscreenSceneBlueprint {
 
     override val id: String = "default"
@@ -74,7 +64,6 @@
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
-        val burnIn = rememberBurnIn(clockInteractor)
         val resources = LocalContext.current.resources
 
         LockscreenLongPress(
@@ -88,40 +77,7 @@
                         modifier = Modifier.fillMaxWidth(),
                     ) {
                         with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                        with(clockSection) {
-                            SmallClock(
-                                burnInParams = burnIn.parameters,
-                                onTopChanged = burnIn.onSmallClockTopChanged,
-                                modifier = Modifier.fillMaxWidth(),
-                            )
-                        }
-                        with(smartSpaceSection) {
-                            SmartSpace(
-                                burnInParams = burnIn.parameters,
-                                onTopChanged = burnIn.onSmartspaceTopChanged,
-                                modifier =
-                                    Modifier.fillMaxWidth()
-                                        .padding(
-                                            top = { viewModel.getSmartSpacePaddingTop(resources) },
-                                        )
-                                        .padding(
-                                            bottom =
-                                                dimensionResource(
-                                                    R.dimen.keyguard_status_view_bottom_margin
-                                                ),
-                                        ),
-                            )
-                        }
-
-                        if (viewModel.isLargeClockVisible) {
-                            Spacer(modifier = Modifier.weight(weight = 1f))
-                            with(clockSection) {
-                                LargeClock(
-                                    modifier = Modifier.fillMaxWidth(),
-                                )
-                            }
-                        }
-
+                        with(clockSection) { DefaultClockLayout() }
                         with(mediaCarouselSection) { MediaCarousel() }
 
                         if (viewModel.areNotificationsVisible(resources = resources)) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index c422c4b..9abfa42 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -17,19 +17,14 @@
 package com.android.systemui.keyguard.ui.composable.blueprint
 
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.padding
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -38,10 +33,8 @@
 import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
 import com.android.systemui.keyguard.ui.composable.section.NotificationSection
 import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
@@ -58,14 +51,12 @@
     private val viewModel: LockscreenContentViewModel,
     private val statusBarSection: StatusBarSection,
     private val clockSection: DefaultClockSection,
-    private val smartSpaceSection: SmartSpaceSection,
     private val notificationSection: NotificationSection,
     private val lockSection: LockSection,
     private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
     private val bottomAreaSection: BottomAreaSection,
     private val settingsMenuSection: SettingsMenuSection,
     private val mediaCarouselSection: MediaCarouselSection,
-    private val clockInteractor: KeyguardClockInteractor,
 ) : ComposableLockscreenSceneBlueprint {
 
     override val id: String = "shortcuts-besides-udfps"
@@ -73,7 +64,6 @@
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
-        val burnIn = rememberBurnIn(clockInteractor)
         val resources = LocalContext.current.resources
 
         LockscreenLongPress(
@@ -87,36 +77,7 @@
                         modifier = Modifier.fillMaxWidth(),
                     ) {
                         with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                        with(clockSection) {
-                            SmallClock(
-                                onTopChanged = burnIn.onSmallClockTopChanged,
-                                modifier = Modifier.fillMaxWidth(),
-                                burnInParams = burnIn.parameters,
-                            )
-                        }
-                        with(smartSpaceSection) {
-                            SmartSpace(
-                                burnInParams = burnIn.parameters,
-                                onTopChanged = burnIn.onSmartspaceTopChanged,
-                                modifier =
-                                    Modifier.fillMaxWidth()
-                                        .padding(
-                                            top = { viewModel.getSmartSpacePaddingTop(resources) }
-                                        )
-                                        .padding(
-                                            bottom =
-                                                dimensionResource(
-                                                    R.dimen.keyguard_status_view_bottom_margin
-                                                )
-                                        ),
-                            )
-                        }
-
-                        if (viewModel.isLargeClockVisible) {
-                            Spacer(modifier = Modifier.weight(weight = 1f))
-                            with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
-                        }
-
+                        with(clockSection) { DefaultClockLayout() }
                         with(mediaCarouselSection) { MediaCarousel() }
 
                         if (viewModel.areNotificationsVisible(resources = resources)) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index d218425..652412d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -27,7 +26,6 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntRect
@@ -35,7 +33,6 @@
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.padding
 import com.android.systemui.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -44,7 +41,6 @@
 import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
 import com.android.systemui.keyguard.ui.composable.section.NotificationSection
 import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import com.android.systemui.res.R
@@ -65,14 +61,12 @@
     private val viewModel: LockscreenContentViewModel,
     private val statusBarSection: StatusBarSection,
     private val clockSection: DefaultClockSection,
-    private val smartSpaceSection: SmartSpaceSection,
     private val notificationSection: NotificationSection,
     private val lockSection: LockSection,
     private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
     private val bottomAreaSection: BottomAreaSection,
     private val settingsMenuSection: SettingsMenuSection,
     private val mediaCarouselSection: MediaCarouselSection,
-    private val clockInteractor: KeyguardClockInteractor,
     private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
 ) : ComposableLockscreenSceneBlueprint {
 
@@ -81,8 +75,6 @@
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
-        val burnIn = rememberBurnIn(clockInteractor)
-        val resources = LocalContext.current.resources
 
         LockscreenLongPress(
             viewModel = viewModel.longPress,
@@ -102,41 +94,7 @@
                                 modifier = Modifier.fillMaxHeight().weight(weight = 1f),
                                 horizontalAlignment = Alignment.CenterHorizontally,
                             ) {
-                                with(clockSection) {
-                                    SmallClock(
-                                        burnInParams = burnIn.parameters,
-                                        onTopChanged = burnIn.onSmallClockTopChanged,
-                                        modifier = Modifier.fillMaxWidth(),
-                                    )
-                                }
-
-                                with(smartSpaceSection) {
-                                    SmartSpace(
-                                        burnInParams = burnIn.parameters,
-                                        onTopChanged = burnIn.onSmartspaceTopChanged,
-                                        modifier =
-                                            Modifier.fillMaxWidth()
-                                                .padding(
-                                                    top = {
-                                                        viewModel.getSmartSpacePaddingTop(resources)
-                                                    },
-                                                )
-                                                .padding(
-                                                    bottom =
-                                                        dimensionResource(
-                                                            R.dimen
-                                                                .keyguard_status_view_bottom_margin
-                                                        )
-                                                ),
-                                    )
-                                }
-
-                                if (viewModel.isLargeClockVisible) {
-                                    Spacer(modifier = Modifier.weight(weight = 1f))
-                                    with(clockSection) { LargeClock() }
-                                    Spacer(modifier = Modifier.weight(weight = 1f))
-                                }
-
+                                with(clockSection) { DefaultClockLayout() }
                                 with(mediaCarouselSection) { MediaCarousel() }
                             }
                             with(notificationSection) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
index 152cc67..1ab2bc76 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
@@ -18,27 +18,37 @@
 
 import android.view.ViewGroup
 import android.widget.FrameLayout
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.viewinterop.AndroidView
-import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.modifiers.padding
-import com.android.keyguard.KeyguardClockSwitch
 import com.android.systemui.customization.R as customizationR
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smartspaceElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition.defaultClockTransitions
+import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
 import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
 import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.res.R
 import javax.inject.Inject
 
 /** Provides small clock and large clock composables for the default clock face. */
@@ -48,112 +58,152 @@
     private val viewModel: KeyguardClockViewModel,
     private val clockInteractor: KeyguardClockInteractor,
     private val aodBurnInViewModel: AodBurnInViewModel,
+    private val lockscreenContentViewModel: LockscreenContentViewModel,
+    private val smartSpaceSection: SmartSpaceSection,
 ) {
+    @Composable
+    fun DefaultClockLayout(
+        modifier: Modifier = Modifier,
+    ) {
+        val isLargeClockVisible by viewModel.isLargeClockVisible.collectAsState()
+        val burnIn = rememberBurnIn(clockInteractor)
+        val currentScene =
+            if (isLargeClockVisible) {
+                largeClockScene
+            } else {
+                smallClockScene
+            }
+
+        LaunchedEffect(isLargeClockVisible) {
+            if (isLargeClockVisible) {
+                burnIn.onSmallClockTopChanged(null)
+            }
+        }
+
+        SceneTransitionLayout(
+            modifier = modifier,
+            currentScene = currentScene,
+            onChangeScene = {},
+            transitions = defaultClockTransitions,
+        ) {
+            scene(smallClockScene) {
+                Column {
+                    SmallClock(
+                        burnInParams = burnIn.parameters,
+                        onTopChanged = burnIn.onSmallClockTopChanged,
+                        modifier = Modifier.element(smallClockElementKey).fillMaxWidth()
+                    )
+                    SmartSpaceContent()
+                }
+            }
+
+            scene(largeClockScene) {
+                Column {
+                    SmartSpaceContent()
+                    LargeClock(modifier = Modifier.element(largeClockElementKey).fillMaxWidth())
+                }
+            }
+        }
+    }
 
     @Composable
-    fun SceneScope.SmallClock(
+    private fun SceneScope.SmartSpaceContent(
+        modifier: Modifier = Modifier,
+    ) {
+        val burnIn = rememberBurnIn(clockInteractor)
+        val resources = LocalContext.current.resources
+
+        MovableElement(key = smartspaceElementKey, modifier = modifier) {
+            content {
+                with(smartSpaceSection) {
+                    this@SmartSpaceContent.SmartSpace(
+                        burnInParams = burnIn.parameters,
+                        onTopChanged = burnIn.onSmartspaceTopChanged,
+                        modifier =
+                            Modifier.fillMaxWidth()
+                                .padding(
+                                    top = {
+                                        lockscreenContentViewModel.getSmartSpacePaddingTop(
+                                            resources
+                                        )
+                                    },
+                                    bottom = {
+                                        resources.getDimensionPixelSize(
+                                            R.dimen.keyguard_status_view_bottom_margin
+                                        )
+                                    }
+                                ),
+                    )
+                }
+            }
+        }
+    }
+
+    @Composable
+    private fun SceneScope.SmallClock(
         burnInParams: BurnInParameters,
         onTopChanged: (top: Float?) -> Unit,
         modifier: Modifier = Modifier,
     ) {
-        val clockSize by viewModel.clockSize.collectAsState()
         val currentClock by viewModel.currentClock.collectAsState()
-        viewModel.clock = currentClock
-
-        if (clockSize != KeyguardClockSwitch.SMALL || currentClock?.smallClock?.view == null) {
-            onTopChanged(null)
+        if (currentClock?.smallClock?.view == null) {
             return
         }
+        viewModel.clock = currentClock
 
-        val view = LocalView.current
+        val context = LocalContext.current
 
-        DisposableEffect(view) {
-            clockInteractor.clockEventController.registerListeners(view)
-
-            onDispose { clockInteractor.clockEventController.unregisterListeners() }
-        }
-
-        MovableElement(
-            key = ClockElementKey,
-            modifier = modifier,
-        ) {
-            content {
-                AndroidView(
-                    factory = { context ->
-                        FrameLayout(context).apply {
-                            val newClockView = checkNotNull(currentClock).smallClock.view
-                            (newClockView.parent as? ViewGroup)?.removeView(newClockView)
-                            addView(newClockView)
-                        }
-                    },
-                    modifier =
-                        Modifier.padding(
-                                horizontal =
-                                    dimensionResource(customizationR.dimen.clock_padding_start)
-                            )
-                            .padding(top = { viewModel.getSmallClockTopMargin(view.context) })
-                            .onTopPlacementChanged(onTopChanged)
-                            .burnInAware(
-                                viewModel = aodBurnInViewModel,
-                                params = burnInParams,
-                            ),
-                    update = {
-                        val newClockView = checkNotNull(currentClock).smallClock.view
-                        it.removeAllViews()
-                        (newClockView.parent as? ViewGroup)?.removeView(newClockView)
-                        it.addView(newClockView)
-                    },
-                )
-            }
-        }
+        AndroidView(
+            factory = { context ->
+                FrameLayout(context).apply {
+                    val newClockView = checkNotNull(currentClock).smallClock.view
+                    (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                    addView(newClockView)
+                }
+            },
+            update = {
+                val newClockView = checkNotNull(currentClock).smallClock.view
+                it.removeAllViews()
+                (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                it.addView(newClockView)
+            },
+            modifier =
+                modifier
+                    .padding(
+                        horizontal = dimensionResource(customizationR.dimen.clock_padding_start)
+                    )
+                    .padding(top = { viewModel.getSmallClockTopMargin(context) })
+                    .onTopPlacementChanged(onTopChanged)
+                    .burnInAware(
+                        viewModel = aodBurnInViewModel,
+                        params = burnInParams,
+                    ),
+        )
     }
 
     @Composable
-    fun SceneScope.LargeClock(modifier: Modifier = Modifier) {
-        val clockSize by viewModel.clockSize.collectAsState()
+    private fun SceneScope.LargeClock(modifier: Modifier = Modifier) {
         val currentClock by viewModel.currentClock.collectAsState()
         viewModel.clock = currentClock
-
-        if (clockSize != KeyguardClockSwitch.LARGE) {
-            return
-        }
-
         if (currentClock?.largeClock?.view == null) {
             return
         }
 
-        val view = LocalView.current
-
-        DisposableEffect(view) {
-            clockInteractor.clockEventController.registerListeners(view)
-
-            onDispose { clockInteractor.clockEventController.unregisterListeners() }
-        }
-
-        MovableElement(
-            key = ClockElementKey,
-            modifier = modifier,
-        ) {
-            content {
-                AndroidView(
-                    factory = { context ->
-                        FrameLayout(context).apply {
-                            val newClockView = checkNotNull(currentClock).largeClock.view
-                            (newClockView.parent as? ViewGroup)?.removeView(newClockView)
-                            addView(newClockView)
-                        }
-                    },
-                    update = {
-                        val newClockView = checkNotNull(currentClock).largeClock.view
-                        it.removeAllViews()
-                        (newClockView.parent as? ViewGroup)?.removeView(newClockView)
-                        it.addView(newClockView)
-                    },
-                    modifier = Modifier.fillMaxSize()
-                )
-            }
-        }
+        AndroidView(
+            factory = { context ->
+                FrameLayout(context).apply {
+                    val newClockView = checkNotNull(currentClock).largeClock.view
+                    (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                    addView(newClockView)
+                }
+            },
+            update = {
+                val newClockView = checkNotNull(currentClock).largeClock.view
+                it.removeAllViews()
+                (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                it.addView(newClockView)
+            },
+            modifier = modifier.fillMaxSize()
+        )
     }
 }
-
-private val ClockElementKey = ElementKey("Clock")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
index 9b71844..d1cc53e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
@@ -33,7 +33,6 @@
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
-import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
 import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
@@ -60,7 +59,7 @@
         modifier: Modifier = Modifier,
     ) {
         Column(
-            modifier = modifier.element(SmartSpaceElementKey).onTopPlacementChanged(onTopChanged),
+            modifier = modifier.onTopPlacementChanged(onTopChanged),
         ) {
             if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) {
                 return
@@ -192,5 +191,3 @@
         )
     }
 }
-
-private val SmartSpaceElementKey = ElementKey("SmartSpace")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 0b9f503..51464d0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -73,6 +73,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
@@ -152,27 +153,29 @@
     mediaHost: MediaHost,
     modifier: Modifier = Modifier,
 ) {
-    val isSplitShade by viewModel.isSplitShade.collectAsState()
-    if (isSplitShade) {
-        SplitShade(
-            viewModel = viewModel,
-            createTintedIconManager = createTintedIconManager,
-            createBatteryMeterViewController = createBatteryMeterViewController,
-            statusBarIconController = statusBarIconController,
-            mediaCarouselController = mediaCarouselController,
-            mediaHost = mediaHost,
-            modifier = modifier,
-        )
-    } else {
-        SingleShade(
-            viewModel = viewModel,
-            createTintedIconManager = createTintedIconManager,
-            createBatteryMeterViewController = createBatteryMeterViewController,
-            statusBarIconController = statusBarIconController,
-            mediaCarouselController = mediaCarouselController,
-            mediaHost = mediaHost,
-            modifier = modifier,
-        )
+    val shadeMode by viewModel.shadeMode.collectAsState()
+    when (shadeMode) {
+        is ShadeMode.Single ->
+            SingleShade(
+                viewModel = viewModel,
+                createTintedIconManager = createTintedIconManager,
+                createBatteryMeterViewController = createBatteryMeterViewController,
+                statusBarIconController = statusBarIconController,
+                mediaCarouselController = mediaCarouselController,
+                mediaHost = mediaHost,
+                modifier = modifier,
+            )
+        is ShadeMode.Split ->
+            SplitShade(
+                viewModel = viewModel,
+                createTintedIconManager = createTintedIconManager,
+                createBatteryMeterViewController = createBatteryMeterViewController,
+                statusBarIconController = statusBarIconController,
+                mediaCarouselController = mediaCarouselController,
+                mediaHost = mediaHost,
+                modifier = modifier,
+            )
+        is ShadeMode.Dual -> error("Dual shade is not yet implemented!")
     }
 }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index b9b472f..6cff30c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -21,6 +21,7 @@
 import androidx.compose.animation.core.SpringSpec
 import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
 
 /**
@@ -147,13 +148,16 @@
         }
 
     // Animate the progress to its target value.
-    launch { animatable.animateTo(targetProgress, animationSpec) }
-        .invokeOnCompletion {
-            // Settle the state to Idle(target). Note that this will do nothing if this transition
-            // was replaced/interrupted by another one, and this also runs if this coroutine is
-            // cancelled, i.e. if [this] coroutine scope is cancelled.
-            layoutState.finishTransition(transition, target)
-        }
+    transition.job =
+        launch { animatable.animateTo(targetProgress, animationSpec) }
+            .apply {
+                invokeOnCompletion {
+                    // Settle the state to Idle(target). Note that this will do nothing if this
+                    // transition was replaced/interrupted by another one, and this also runs if
+                    // this coroutine is cancelled, i.e. if [this] coroutine scope is cancelled.
+                    layoutState.finishTransition(transition, target)
+                }
+            }
 
     return transition
 }
@@ -174,8 +178,13 @@
      */
     lateinit var animatable: Animatable<Float, AnimationVector1D>
 
+    /** The job that is animating [animatable]. */
+    lateinit var job: Job
+
     override val progress: Float
         get() = animatable.value
+
+    override fun finish(): Job = job
 }
 
 // TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index b94e49b..6114499 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -20,6 +20,7 @@
 
 import android.util.Log
 import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.animation.core.SpringSpec
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.runtime.getValue
@@ -96,9 +97,15 @@
             return false
         }
 
+        val swipeTransition = dragController.swipeTransition
+
+        // Don't intercept a transition that is finishing.
+        if (swipeTransition.isFinishing) {
+            return false
+        }
+
         // Only intercept the current transition if one of the 2 swipes results is also a transition
         // between the same pair of scenes.
-        val swipeTransition = dragController.swipeTransition
         val fromScene = swipeTransition._currentScene
         val swipes = computeSwipes(fromScene, startedPosition, pointersDown = 1)
         val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromScene)
@@ -149,15 +156,24 @@
 
         val fromScene = layoutImpl.scene(transitionState.currentScene)
         val swipes = computeSwipes(fromScene, startedPosition, pointersDown)
-        val result = swipes.findUserActionResult(fromScene, overSlop, true)
-
-        // As we were unable to locate a valid target scene, the initial SwipeTransition cannot be
-        // defined. Consequently, a simple NoOp Controller will be returned.
-        if (result == null) return NoOpDragController
+        val result =
+            swipes.findUserActionResult(fromScene, overSlop, true)
+            // As we were unable to locate a valid target scene, the initial SwipeTransition
+            // cannot be defined. Consequently, a simple NoOp Controller will be returned.
+            ?: return NoOpDragController
 
         return updateDragController(
             swipes = swipes,
-            swipeTransition = SwipeTransition(fromScene, result, swipes, layoutImpl, orientation)
+            swipeTransition =
+                SwipeTransition(
+                    layoutImpl.state,
+                    coroutineScope,
+                    fromScene,
+                    result,
+                    swipes,
+                    layoutImpl,
+                    orientation,
+                )
         )
     }
 
@@ -277,7 +293,7 @@
      * @return the consumed delta
      */
     override fun onDrag(delta: Float) {
-        if (delta == 0f || !isDrivingTransition) return
+        if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) return
         swipeTransition.dragOffset += delta
 
         val (fromScene, acceleratedOffset) =
@@ -305,6 +321,8 @@
         ) {
             val swipeTransition =
                 SwipeTransition(
+                        layoutState = layoutState,
+                        coroutineScope = draggableHandler.coroutineScope,
                         fromScene = fromScene,
                         result = result,
                         swipes = swipes,
@@ -355,15 +373,9 @@
         }
     }
 
-    private fun snapToScene(scene: SceneKey) {
-        if (!isDrivingTransition) return
-        swipeTransition.cancelOffsetAnimation()
-        layoutState.finishTransition(swipeTransition, idleScene = scene)
-    }
-
     override fun onStop(velocity: Float, canChangeScene: Boolean) {
         // The state was changed since the drag started; don't do anything.
-        if (!isDrivingTransition) {
+        if (!isDrivingTransition || swipeTransition.isFinishing) {
             return
         }
 
@@ -389,7 +401,7 @@
                 coroutineScope = draggableHandler.coroutineScope,
                 initialVelocity = velocity,
                 targetOffset = targetOffset,
-                onAnimationCompleted = { snapToScene(targetScene.key) }
+                targetScene = targetScene.key,
             )
         }
 
@@ -451,12 +463,14 @@
                 val result = swipes.findUserActionResultStrict(velocity)
                 if (result == null) {
                     // We will not animate
-                    snapToScene(fromScene.key)
+                    swipeTransition.snapToScene(fromScene.key)
                     return
                 }
 
                 val newSwipeTransition =
                     SwipeTransition(
+                            layoutState = layoutState,
+                            coroutineScope = draggableHandler.coroutineScope,
                             fromScene = fromScene,
                             result = result,
                             swipes = swipes,
@@ -514,6 +528,8 @@
 }
 
 private fun SwipeTransition(
+    layoutState: BaseSceneTransitionLayoutState,
+    coroutineScope: CoroutineScope,
     fromScene: Scene,
     result: UserActionResult,
     swipes: Swipes,
@@ -530,6 +546,8 @@
         }
 
     return SwipeTransition(
+        layoutState = layoutState,
+        coroutineScope = coroutineScope,
         key = result.transitionKey,
         _fromScene = fromScene,
         _toScene = layoutImpl.scene(result.toScene),
@@ -541,6 +559,8 @@
 
 private fun SwipeTransition(old: SwipeTransition): SwipeTransition {
     return SwipeTransition(
+            layoutState = old.layoutState,
+            coroutineScope = old.coroutineScope,
             key = old.key,
             _fromScene = old._fromScene,
             _toScene = old._toScene,
@@ -555,6 +575,8 @@
 }
 
 private class SwipeTransition(
+    val layoutState: BaseSceneTransitionLayoutState,
+    val coroutineScope: CoroutineScope,
     val key: TransitionKey?,
     val _fromScene: Scene,
     val _toScene: Scene,
@@ -573,7 +595,7 @@
             // Important: If we are going to return early because distance is equal to 0, we should
             // still make sure we read the offset before returning so that the calling code still
             // subscribes to the offset value.
-            val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
+            val offset = offsetAnimation?.animatable?.value ?: dragOffset
 
             val distance = distance()
             if (distance == DistanceUnspecified) {
@@ -588,20 +610,11 @@
     /** The current offset caused by the drag gesture. */
     var dragOffset by mutableFloatStateOf(0f)
 
-    /**
-     * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture.
-     */
-    var isAnimatingOffset by mutableStateOf(false)
+    /** The offset animation that animates the offset once the user lifts their finger. */
+    private var offsetAnimation: OffsetAnimation? by mutableStateOf(null)
 
-    // If we are not animating offset, it means the offset is being driven by the user's finger.
     override val isUserInputOngoing: Boolean
-        get() = !isAnimatingOffset
-
-    /** The animatable used to animate the offset once the user lifted its finger. */
-    val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold)
-
-    /** Job to check that there is at most one offset animation in progress. */
-    private var offsetAnimationJob: Job? = null
+        get() = offsetAnimation == null
 
     /**
      * The [TransformationSpecImpl] associated to this transition.
@@ -617,6 +630,10 @@
 
     private var lastDistance = DistanceUnspecified
 
+    /** Whether [TransitionState.Transition.finish] was called on this transition. */
+    var isFinishing = false
+        private set
+
     /**
      * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
      * or to the left of [toScene].
@@ -647,25 +664,21 @@
         return distance
     }
 
-    /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
-    private fun startOffsetAnimation(job: () -> Job) {
+    /** Ends any previous [offsetAnimation] and runs the new [animation]. */
+    private fun startOffsetAnimation(animation: () -> OffsetAnimation): OffsetAnimation {
         cancelOffsetAnimation()
-        offsetAnimationJob = job()
+        return animation().also { offsetAnimation = it }
     }
 
     /** Cancel any ongoing offset animation. */
     // TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at
     // the same time.
     fun cancelOffsetAnimation() {
-        offsetAnimationJob?.cancel()
-        finishOffsetAnimation()
-    }
+        val animation = offsetAnimation ?: return
+        offsetAnimation = null
 
-    fun finishOffsetAnimation() {
-        if (isAnimatingOffset) {
-            isAnimatingOffset = false
-            dragOffset = offsetAnimatable.value
-        }
+        dragOffset = animation.animatable.value
+        animation.job.cancel()
     }
 
     fun animateOffset(
@@ -673,31 +686,73 @@
         coroutineScope: CoroutineScope,
         initialVelocity: Float,
         targetOffset: Float,
-        onAnimationCompleted: () -> Unit,
-    ) {
-        startOffsetAnimation {
-            coroutineScope.launch {
-                animateOffset(targetOffset, initialVelocity)
-                onAnimationCompleted()
+        targetScene: SceneKey,
+    ): OffsetAnimation {
+        return startOffsetAnimation {
+            val animatable = Animatable(dragOffset, OffsetVisibilityThreshold)
+            val job =
+                coroutineScope
+                    .launch {
+                        animatable.animateTo(
+                            targetValue = targetOffset,
+                            animationSpec = swipeSpec,
+                            initialVelocity = initialVelocity,
+                        )
+                    }
+                    // Make sure that we settle to target scene at the end of the animation or if
+                    // the animation is cancelled.
+                    .apply { invokeOnCompletion { snapToScene(targetScene) } }
+
+            OffsetAnimation(animatable, job)
+        }
+    }
+
+    fun snapToScene(scene: SceneKey) {
+        if (layoutState.transitionState != this) return
+        cancelOffsetAnimation()
+        layoutState.finishTransition(this, idleScene = scene)
+    }
+
+    override fun finish(): Job {
+        if (isFinishing) return requireNotNull(offsetAnimation).job
+        isFinishing = true
+
+        // If we were already animating the offset, simply return the job.
+        offsetAnimation?.let {
+            return it.job
+        }
+
+        // Animate to the current scene.
+        val targetScene = currentScene
+        val targetOffset =
+            if (targetScene == fromScene) {
+                0f
+            } else {
+                val distance = distance()
+                check(distance != DistanceUnspecified) {
+                    "targetScene != fromScene but distance is unspecified"
+                }
+                distance
             }
-        }
+
+        val animation =
+            animateOffset(
+                coroutineScope = coroutineScope,
+                initialVelocity = 0f,
+                targetOffset = targetOffset,
+                targetScene = currentScene,
+            )
+        check(offsetAnimation == animation)
+        return animation.job
     }
 
-    private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) {
-        if (!isAnimatingOffset) {
-            offsetAnimatable.snapTo(dragOffset)
-        }
-        isAnimatingOffset = true
+    internal class OffsetAnimation(
+        /** The animatable used to animate the offset. */
+        val animatable: Animatable<Float, AnimationVector1D>,
 
-        val animationSpec = transformationSpec
-        offsetAnimatable.animateTo(
-            targetValue = targetOffset,
-            animationSpec = swipeSpec,
-            initialVelocity = initialVelocity,
-        )
-
-        finishOffsetAnimation()
-    }
+        /** The job in which [animatable] is animated. */
+        val job: Job,
+    )
 
     companion object {
         const val DistanceUnspecified = 0f
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index cdc4778..be066fd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -133,18 +133,14 @@
         if (shouldComposeMovableElement) {
             val movableContent: MovableElementContent =
                 layoutImpl.movableContents[element]
-                    ?: movableContentOf {
-                            contentScope: MovableElementContentScope,
-                            content: @Composable MovableElementContentScope.() -> Unit ->
-                            contentScope.content()
-                        }
+                    ?: movableContentOf { content: @Composable () -> Unit -> content() }
                         .also { layoutImpl.movableContents[element] = it }
 
             // Important: Don't introduce any parent Box or other layout here, because contentScope
             // delegates its BoxScope implementation to the Box where this content() function is
             // called, so it's important that this movableContent is composed directly under that
             // Box.
-            movableContent(contentScope, content)
+            movableContent { contentScope.content() }
         } else {
             // If we are not composed, we still need to lay out an empty space with the same *target
             // size* as its movable content, i.e. the same *size when idle*. During transitions,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 1670e9c..25b0895 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -38,15 +38,8 @@
 import com.android.compose.ui.util.lerp
 import kotlinx.coroutines.CoroutineScope
 
-/**
- * The type for the content of movable elements.
- *
- * TODO(b/317972419): Revert back to make this movable content have a single @Composable lambda
- *   parameter.
- */
-internal typealias MovableElementContent =
-    @Composable
-    (MovableElementContentScope, @Composable MovableElementContentScope.() -> Unit) -> Unit
+/** The type for the content of movable elements. */
+internal typealias MovableElementContent = @Composable (@Composable () -> Unit) -> Unit
 
 @Stable
 internal class SceneTransitionLayoutImpl(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 0fa19bb..86124df 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -31,6 +31,7 @@
 import com.android.compose.animation.scene.transition.link.StateLink
 import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.channels.Channel
 
 /**
@@ -188,13 +189,8 @@
         val fromScene: SceneKey,
 
         /** The scene this transition is going to. Can't be the same as fromScene */
-        val toScene: SceneKey
+        val toScene: SceneKey,
     ) : TransitionState {
-
-        init {
-            check(fromScene != toScene)
-        }
-
         /**
          * The progress of the transition. This is usually in the `[0; 1]` range, but it can also be
          * less than `0` or greater than `1` when using transitions with a spring AnimationSpec or
@@ -208,6 +204,21 @@
         /** Whether user input is currently driving the transition. */
         abstract val isUserInputOngoing: Boolean
 
+        init {
+            check(fromScene != toScene)
+        }
+
+        /**
+         * Force this transition to finish and animate to [currentScene], so that this transition
+         * progress will settle to either 0% (if [currentScene] == [fromScene]) or 100% (if
+         * [currentScene] == [toScene]) in a finite amount of time.
+         *
+         * @return the [Job] that animates the progress to [currentScene]. It can be used to wait
+         *   until the animation is complete or cancel it to snap to [currentScene]. Calling
+         *   [finish] multiple times will return the same [Job].
+         */
+        abstract fun finish(): Job
+
         /**
          * Whether we are transitioning. If [from] or [to] is empty, we will also check that they
          * match the scenes we are animating from and/or to.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
index 33b57b2..73393a1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -18,6 +18,7 @@
 
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionState
+import kotlinx.coroutines.Job
 
 /** A linked transition which is driven by a [originalTransition]. */
 internal class LinkedTransition(
@@ -43,4 +44,6 @@
 
     override val progress: Float
         get() = originalTransition.progress
+
+    override fun finish(): Job = originalTransition.finish()
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index eb9b428..1e9a7e2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -39,6 +39,7 @@
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.launch
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -892,6 +893,50 @@
     }
 
     @Test
+    fun finish() = runGestureTest {
+        // Start at scene C.
+        navigateToSceneC()
+
+        // Swipe up from the middle to transition to scene B.
+        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+        onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+        assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true)
+
+        // The current transition can be intercepted.
+        assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
+
+        // Finish the transition.
+        val transition = transitionState as Transition
+        val job = transition.finish()
+        assertTransition(isUserInputOngoing = false)
+
+        // The current transition can not be intercepted anymore.
+        assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isFalse()
+
+        // Calling finish() multiple times returns the same Job.
+        assertThat(transition.finish()).isSameInstanceAs(job)
+        assertThat(transition.finish()).isSameInstanceAs(job)
+        assertThat(transition.finish()).isSameInstanceAs(job)
+
+        // We can join the job to wait for the animation to end.
+        assertTransition()
+        job.join()
+        assertIdle(SceneC)
+    }
+
+    @Test
+    fun finish_cancelled() = runGestureTest {
+        // Swipe up from the middle to transition to scene B.
+        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+        onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+        assertTransition(fromScene = SceneA, toScene = SceneB)
+
+        // Finish the transition and cancel the returned job.
+        (transitionState as Transition).finish().cancelAndJoin()
+        assertIdle(SceneA)
+    }
+
+    @Test
     fun blockTransition() = runGestureTest {
         assertIdle(SceneA)
 
@@ -951,4 +996,15 @@
         assertThat(transition).isNotNull()
         assertThat(transition!!.progress).isEqualTo(-0.1f)
     }
+
+    @Test
+    fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest {
+        // Swipe up from the middle to transition to scene B.
+        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+        val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+        assertTransition(fromScene = SceneA, toScene = SceneB, isUserInputOngoing = true)
+
+        dragController.onDragStopped(velocity = 0f)
+        assertTransition(isUserInputOngoing = false)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 3cbcd73..9baabc3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -28,6 +28,8 @@
 import com.android.compose.test.runMonotonicClockTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.job
 import kotlinx.coroutines.launch
 import org.junit.Rule
 import org.junit.Test
@@ -37,16 +39,6 @@
 class SceneTransitionLayoutStateTest {
     @get:Rule val rule = createComposeRule()
 
-    class TestableTransition(
-        fromScene: SceneKey,
-        toScene: SceneKey,
-    ) : TransitionState.Transition(fromScene, toScene) {
-        override var currentScene: SceneKey = fromScene
-        override var progress: Float = 0.0f
-        override var isInitiatedByUserInput: Boolean = false
-        override var isUserInputOngoing: Boolean = false
-    }
-
     @Test
     fun isTransitioningTo_idle() {
         val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
@@ -83,25 +75,31 @@
         assertThat(transition).isNotNull()
         assertThat(state.transitionState).isEqualTo(transition)
 
-        testScheduler.advanceUntilIdle()
+        transition!!.finish().join()
         assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
     }
 
     @Test
     fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
         val state = MutableSceneTransitionLayoutState(SceneA)
-        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
+
+        val transition = state.setTargetScene(SceneB, coroutineScope = this)
+        assertThat(transition).isNotNull()
         assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNull()
-        testScheduler.advanceUntilIdle()
+
+        transition!!.finish().join()
         assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
     }
 
     @Test
     fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
         val state = MutableSceneTransitionLayoutState(SceneA)
+
         assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
-        assertThat(state.setTargetScene(SceneC, coroutineScope = this)).isNotNull()
-        testScheduler.advanceUntilIdle()
+        val transition = state.setTargetScene(SceneC, coroutineScope = this)
+        assertThat(transition).isNotNull()
+
+        transition!!.finish().join()
         assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC))
     }
 
@@ -127,11 +125,22 @@
         assertThat(state.transitionState).isEqualTo(transition)
 
         // Cancelling the scope/job still sets the state to Idle(targetScene).
-        job.cancel()
-        testScheduler.advanceUntilIdle()
+        job.cancelAndJoin()
         assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
     }
 
+    @Test
+    fun transition_finishReturnsTheSameJobWhenCalledMultipleTimes() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        val transition = state.setTargetScene(SceneB, coroutineScope = this)
+        assertThat(transition).isNotNull()
+
+        val job = transition!!.finish()
+        assertThat(transition.finish()).isSameInstanceAs(job)
+        assertThat(transition.finish()).isSameInstanceAs(job)
+        assertThat(transition.finish()).isSameInstanceAs(job)
+    }
+
     private fun setupLinkedStates(
         parentInitialScene: SceneKey = SceneC,
         childInitialScene: SceneKey = SceneA,
@@ -159,7 +168,7 @@
     fun linkedTransition_startsLinkAndFinishesLinkInToState() {
         val (parentState, childState) = setupLinkedStates()
 
-        val childTransition = TestableTransition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB)
 
         childState.startTransition(childTransition, null)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -195,7 +204,7 @@
             MutableSceneTransitionLayoutState(SceneA, stateLinks = link)
                 as BaseSceneTransitionLayoutState
 
-        val childTransition = TestableTransition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB)
 
         childState.startTransition(childTransition, null)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -212,12 +221,13 @@
     fun linkedTransition_linkProgressIsEqual() {
         val (parentState, childState) = setupLinkedStates()
 
-        val childTransition = TestableTransition(SceneA, SceneB)
+        var progress = 0f
+        val childTransition = transition(SceneA, SceneB, progress = { progress })
 
         childState.startTransition(childTransition, null)
         assertThat(parentState.currentTransition?.progress).isEqualTo(0f)
 
-        childTransition.progress = .5f
+        progress = .5f
         assertThat(parentState.currentTransition?.progress).isEqualTo(.5f)
     }
 
@@ -225,7 +235,7 @@
     fun linkedTransition_reverseTransitionIsNotLinked() {
         val (parentState, childState) = setupLinkedStates()
 
-        val childTransition = TestableTransition(SceneB, SceneA)
+        val childTransition = transition(SceneB, SceneA)
 
         childState.startTransition(childTransition, null)
         assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue()
@@ -240,7 +250,7 @@
     fun linkedTransition_startsLinkAndFinishesLinkInFromState() {
         val (parentState, childState) = setupLinkedStates()
 
-        val childTransition = TestableTransition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB)
         childState.startTransition(childTransition, null)
 
         childState.finishTransition(childTransition, SceneA)
@@ -252,7 +262,7 @@
     fun linkedTransition_startsLinkAndFinishesLinkInUnknownState() {
         val (parentState, childState) = setupLinkedStates()
 
-        val childTransition = TestableTransition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB)
         childState.startTransition(childTransition, null)
 
         childState.finishTransition(childTransition, SceneD)
@@ -264,8 +274,8 @@
     fun linkedTransition_startsLinkButLinkedStateIsTakenOver() {
         val (parentState, childState) = setupLinkedStates()
 
-        val childTransition = TestableTransition(SceneA, SceneB)
-        val parentTransition = TestableTransition(SceneC, SceneA)
+        val childTransition = transition(SceneA, SceneB)
+        val parentTransition = transition(SceneC, SceneA)
         childState.startTransition(childTransition, null)
         parentState.startTransition(parentTransition, null)
 
@@ -315,9 +325,9 @@
 
     @Test
     fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
         state.startTransition(
-            transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.2f }),
+            transition(from = SceneA, to = TestScenes.SceneB, progress = { 0.2f }),
             transitionKey = null
         )
         assertThat(state.isTransitioning()).isTrue()
@@ -329,14 +339,14 @@
         // Go to the initial scene if it is close to 0.
         assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
         assertThat(state.isTransitioning()).isFalse()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA))
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
     }
 
     @Test
     fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
         state.startTransition(
-            transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.8f }),
+            transition(from = SceneA, to = TestScenes.SceneB, progress = { 0.8f }),
             transitionKey = null
         )
         assertThat(state.isTransitioning()).isTrue()
@@ -354,7 +364,7 @@
     @Test
     fun linkedTransition_fuzzyLinksAreMatchedAndStarted() {
         val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD)
-        val childTransition = TestableTransition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB)
 
         childState.startTransition(childTransition, null)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -370,7 +380,7 @@
         val (parentState, childState) =
             setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD)
 
-        val childTransition = TestableTransition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB)
 
         childState.startTransition(childTransition, null)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -385,7 +395,7 @@
     fun linkedTransition_fuzzyLinksAreNotMatched() {
         val (parentState, childState) =
             setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD)
-        val childTransition = TestableTransition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB)
 
         childState.startTransition(childTransition, null)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -402,18 +412,12 @@
                 sceneTransitions,
             )
         state.startTransition(
-            object :
-                TransitionState.Transition(SceneA, SceneB),
-                TransitionState.HasOverscrollProperties {
-                override val currentScene: SceneKey = SceneA
-                override val progress: Float
-                    get() = progress()
-
-                override val isInitiatedByUserInput: Boolean = false
-                override val isUserInputOngoing: Boolean = false
-                override val isUpOrLeft: Boolean = false
-                override val orientation: Orientation = Orientation.Vertical
-            },
+            transition(
+                from = SceneA,
+                to = SceneB,
+                progress = progress,
+                orientation = Orientation.Vertical,
+            ),
             transitionKey = null
         )
         assertThat(state.isTransitioning()).isTrue()
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
index 238b21e1..153d2b8 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
@@ -16,6 +16,9 @@
 
 package com.android.compose.animation.scene
 
+import androidx.compose.foundation.gestures.Orientation
+import kotlinx.coroutines.Job
+
 /** A utility to easily create a [TransitionState.Transition] in tests. */
 fun transition(
     from: SceneKey,
@@ -23,11 +26,21 @@
     progress: () -> Float = { 0f },
     isInitiatedByUserInput: Boolean = false,
     isUserInputOngoing: Boolean = false,
+    isUpOrLeft: Boolean = false,
+    orientation: Orientation = Orientation.Horizontal,
 ): TransitionState.Transition {
-    return object : TransitionState.Transition(from, to) {
+    return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties {
         override val currentScene: SceneKey = from
-        override val progress: Float = progress()
+        override val progress: Float
+            get() = progress()
+
         override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput
         override val isUserInputOngoing: Boolean = isUserInputOngoing
+        override val isUpOrLeft: Boolean = isUpOrLeft
+        override val orientation: Orientation = orientation
+
+        override fun finish(): Job {
+            error("finish() is not supported in test transitions")
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
new file mode 100644
index 0000000..def63ec
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal
+
+import android.service.dream.dreamManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.Mockito.never
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalDreamStartableTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var underTest: CommunalDreamStartable
+
+    private val dreamManager by lazy { kosmos.dreamManager }
+    private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+    private val powerRepository by lazy { kosmos.fakePowerRepository }
+
+    @Before
+    fun setUp() {
+        underTest =
+            CommunalDreamStartable(
+                    powerInteractor = kosmos.powerInteractor,
+                    keyguardInteractor = kosmos.keyguardInteractor,
+                    keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+                    dreamManager = dreamManager,
+                    bgScope = kosmos.applicationCoroutineScope,
+                )
+                .apply { start() }
+    }
+
+    @Test
+    fun startDreamWhenTransitioningToHub() =
+        testScope.runTest {
+            keyguardRepository.setDreaming(false)
+            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+            whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
+            runCurrent()
+
+            verify(dreamManager, never()).startDream()
+
+            transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+            verify(dreamManager).startDream()
+        }
+
+    @Test
+    fun shouldNotStartDreamWhenIneligibleToDream() =
+        testScope.runTest {
+            keyguardRepository.setDreaming(false)
+            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+            // Not eligible to dream
+            whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(false)
+            transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+            verify(dreamManager, never()).startDream()
+        }
+
+    @Test
+    fun shouldNotStartDreamIfAlreadyDreaming() =
+        testScope.runTest {
+            keyguardRepository.setDreaming(true)
+            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+            whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
+            transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+            verify(dreamManager, never()).startDream()
+        }
+
+    @Test
+    fun shouldNotStartDreamForInvalidTransition() =
+        testScope.runTest {
+            keyguardRepository.setDreaming(true)
+            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+            whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
+
+            // Verify we do not trigger dreaming for any other state besides glanceable hub
+            for (state in KeyguardState.entries) {
+                if (state == KeyguardState.GLANCEABLE_HUB) continue
+                transition(from = KeyguardState.GLANCEABLE_HUB, to = state)
+                verify(dreamManager, never()).startDream()
+            }
+        }
+
+    private suspend fun TestScope.transition(from: KeyguardState, to: KeyguardState) {
+        kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+            from = from,
+            to = to,
+            testScope = this
+        )
+        runCurrent()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index eafd503..a5707e1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -19,8 +19,12 @@
 
 import android.app.smartspace.SmartspaceTarget
 import android.appwidget.AppWidgetProviderInfo
+import android.content.Intent
 import android.content.pm.UserInfo
 import android.os.UserHandle
+import android.os.UserManager
+import android.os.userManager
+import android.provider.Settings
 import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
 import android.widget.RemoteViews
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -50,6 +54,8 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.activityStarter
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
@@ -61,6 +67,9 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.fakeSettings
@@ -73,6 +82,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
@@ -103,6 +114,8 @@
     private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
     private lateinit var sceneInteractor: SceneInteractor
     private lateinit var userTracker: FakeUserTracker
+    private lateinit var activityStarter: ActivityStarter
+    private lateinit var userManager: UserManager
 
     private lateinit var underTest: CommunalInteractor
 
@@ -121,9 +134,13 @@
         communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
         sceneInteractor = kosmos.sceneInteractor
         userTracker = kosmos.fakeUserTracker
+        activityStarter = kosmos.activityStarter
+        userManager = kosmos.userManager
 
         whenever(mainUser.isMain).thenReturn(true)
         whenever(secondaryUser.isMain).thenReturn(false)
+        whenever(userManager.isQuietModeEnabled(any<UserHandle>())).thenReturn(false)
+        whenever(userManager.isManagedProfile(anyInt())).thenReturn(false)
         userRepository.setUserInfos(listOf(mainUser, secondaryUser))
 
         kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
@@ -800,6 +817,16 @@
         }
 
     @Test
+    fun navigateToCommunalWidgetSettings_startsActivity() =
+        testScope.runTest {
+            underTest.navigateToCommunalWidgetSettings()
+            val intentCaptor = argumentCaptor<Intent>()
+            verify(activityStarter)
+                .postStartActivityDismissingKeyguard(capture(intentCaptor), eq(0))
+            assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_COMMUNAL_SETTING)
+        }
+
+    @Test
     fun filterWidgets_whenUserProfileRemoved() =
         testScope.runTest {
             // Keyguard showing, and tutorial completed.
@@ -832,6 +859,63 @@
         }
 
     @Test
+    fun widgetContent_inQuietMode() =
+        testScope.runTest {
+            // Keyguard showing, and tutorial completed.
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+            // Work profile is set up.
+            val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
+            userRepository.setUserInfos(userInfos)
+            userTracker.set(
+                userInfos = userInfos,
+                selectedUserIndex = 0,
+            )
+            runCurrent()
+
+            // Keyguard widgets are allowed.
+            kosmos.fakeSettings.putIntForUser(
+                CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING,
+                AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+                mainUser.id
+            )
+            runCurrent()
+
+            // When work profile is paused.
+            whenever(userManager.isQuietModeEnabled(eq(UserHandle.of(USER_INFO_WORK.id))))
+                .thenReturn(true)
+            whenever(userManager.isManagedProfile(eq(USER_INFO_WORK.id))).thenReturn(true)
+
+            val widgetContent by collectLastValue(underTest.widgetContent)
+            val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
+            val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+            val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
+            val widgets = listOf(widget1, widget2, widget3)
+            widgetRepository.setCommunalWidgets(widgets)
+
+            // The work profile widget is in quiet mode, while other widgets are not.
+            assertThat(widgetContent).hasSize(3)
+            widgetContent!!.forEach { model ->
+                assertThat(model)
+                    .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java)
+            }
+            assertThat(
+                    (widgetContent!![0] as CommunalContentModel.WidgetContent.Widget).inQuietMode
+                )
+                .isTrue()
+            assertThat(
+                    (widgetContent!![1] as CommunalContentModel.WidgetContent.Widget).inQuietMode
+                )
+                .isFalse()
+            assertThat(
+                    (widgetContent!![2] as CommunalContentModel.WidgetContent.Widget).inQuietMode
+                )
+                .isFalse()
+        }
+
+    @Test
     fun widgetContent_containsDisabledWidgets_whenCategoryNotAllowed() =
         testScope.runTest {
             // Communal available, and tutorial completed.
@@ -932,7 +1016,10 @@
     private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
         mock<CommunalWidgetContentModel> {
             whenever(this.appWidgetId).thenReturn(appWidgetId)
-            val providerInfo = mock<AppWidgetProviderInfo>()
+            val providerInfo =
+                mock<AppWidgetProviderInfo>().apply {
+                    widgetCategory = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
+                }
             whenever(providerInfo.profile).thenReturn(UserHandle(userId))
             whenever(this.providerInfo).thenReturn(providerInfo)
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index c670506..86fdaa5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -8,7 +8,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.complication.ComplicationHostViewController
-import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel
 import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.statusbar.BlurUtils
 import com.android.systemui.util.mockito.argumentCaptor
@@ -45,7 +45,7 @@
     @Mock private lateinit var hostViewController: ComplicationHostViewController
     @Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
     @Mock private lateinit var stateController: DreamOverlayStateController
-    @Mock private lateinit var transitionViewModel: DreamOverlayViewModel
+    @Mock private lateinit var transitionViewModel: DreamViewModel
     private val logBuffer = FakeLogBuffer.Factory.create()
     private lateinit var controller: DreamOverlayAnimationsController
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index 9da34da..e34edb4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -26,14 +26,9 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
-import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -219,37 +214,6 @@
             values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
         }
 
-    @Test
-    fun transitionEnded() =
-        testScope.runTest {
-            val values by collectValues(underTest.transitionEnded)
-
-            keyguardTransitionRepository.sendTransitionSteps(
-                listOf(
-                    TransitionStep(DOZING, DREAMING, 0.0f, STARTED),
-                    TransitionStep(DOZING, DREAMING, 1.0f, FINISHED),
-                    TransitionStep(DREAMING, LOCKSCREEN, 0.0f, STARTED),
-                    TransitionStep(DREAMING, LOCKSCREEN, 0.1f, RUNNING),
-                    TransitionStep(DREAMING, LOCKSCREEN, 1.0f, FINISHED),
-                    TransitionStep(LOCKSCREEN, DREAMING, 0.0f, STARTED),
-                    TransitionStep(LOCKSCREEN, DREAMING, 0.5f, RUNNING),
-                    TransitionStep(LOCKSCREEN, DREAMING, 1.0f, FINISHED),
-                    TransitionStep(DREAMING, GONE, 0.0f, STARTED),
-                    TransitionStep(DREAMING, GONE, 0.5f, RUNNING),
-                    TransitionStep(DREAMING, GONE, 1.0f, CANCELED),
-                    TransitionStep(DREAMING, AOD, 0.0f, STARTED),
-                    TransitionStep(DREAMING, AOD, 1.0f, FINISHED),
-                ),
-                testScope,
-            )
-
-            assertThat(values.size).isEqualTo(3)
-            values.forEach {
-                assertThat(it.transitionState == FINISHED || it.transitionState == CANCELED)
-                    .isTrue()
-            }
-        }
-
     private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
         return TransitionStep(
             from = DREAMING,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index 4fe45f6..8c9036a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.userRepository
 import com.google.common.truth.Truth
@@ -562,17 +563,17 @@
         }
 
     @Test
-    fun isSplitShade() =
+    fun shadeMode() =
         testScope.runTest {
-            val isSplitShade by collectLastValue(underTest.isSplitShade)
+            val shadeMode by collectLastValue(underTest.shadeMode)
 
-            shadeRepository.setSplitShade(true)
-            assertThat(isSplitShade).isTrue()
+            shadeRepository.setShadeMode(ShadeMode.Split)
+            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
 
-            shadeRepository.setSplitShade(false)
-            assertThat(isSplitShade).isFalse()
+            shadeRepository.setShadeMode(ShadeMode.Single)
+            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
 
-            shadeRepository.setSplitShade(true)
-            assertThat(isSplitShade).isTrue()
+            shadeRepository.setShadeMode(ShadeMode.Split)
+            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
index cbb84da..31dacdd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
@@ -42,20 +43,20 @@
     private val underTest = kosmos.shadeStartable
 
     @Test
-    fun hydrateSplitShade() =
+    fun hydrateShadeMode() =
         testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, false)
-            val isSplitShade by collectLastValue(shadeInteractor.isSplitShade)
+            val shadeMode by collectLastValue(shadeInteractor.shadeMode)
 
             underTest.start()
-            assertThat(isSplitShade).isFalse()
+            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
 
             overrideResource(R.bool.config_use_split_notification_shade, true)
             fakeConfigurationRepository.onAnyConfigurationChange()
-            assertThat(isSplitShade).isTrue()
+            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
 
             overrideResource(R.bool.config_use_split_notification_shade, false)
             fakeConfigurationRepository.onAnyConfigurationChange()
-            assertThat(isSplitShade).isFalse()
+            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 2e68d12..1c54961 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -37,10 +37,12 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.privacyChipInteractor
 import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.domain.startable.shadeStartable
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -72,6 +74,7 @@
     private val testScope = kosmos.testScope
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
     private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
+    private val shadeRepository by lazy { kosmos.shadeRepository }
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
     private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
@@ -274,4 +277,19 @@
             assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.toScene)
                 .isEqualTo(Scenes.QuickSettings)
         }
+
+    @Test
+    fun shadeMode() =
+        testScope.runTest {
+            val shadeMode by collectLastValue(underTest.shadeMode)
+
+            shadeRepository.setShadeMode(ShadeMode.Split)
+            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+
+            shadeRepository.setShadeMode(ShadeMode.Single)
+            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
+
+            shadeRepository.setShadeMode(ShadeMode.Split)
+            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index deb1976..e683f34c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -21,13 +21,10 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -80,13 +77,13 @@
     init {
         kosmos.aodBurnInViewModel = aodBurnInViewModel
     }
+
     val testScope = kosmos.testScope
     val configurationRepository = kosmos.fakeConfigurationRepository
     val keyguardRepository = kosmos.fakeKeyguardRepository
     val keyguardInteractor = kosmos.keyguardInteractor
     val keyguardRootViewModel = kosmos.keyguardRootViewModel
     val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
-    val communalInteractor = kosmos.communalInteractor
     val shadeRepository = kosmos.shadeRepository
     val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor
     val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper
@@ -239,7 +236,7 @@
         }
 
     @Test
-    fun glanceableHubAlpha() =
+    fun glanceableHubAlpha_lockscreenToHub() =
         testScope.runTest {
             val alpha by collectLastValue(underTest.glanceableHubAlpha)
 
@@ -278,12 +275,6 @@
                     value = 1f,
                 )
             )
-            val idleTransitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(CommunalScenes.Communal)
-                )
-            communalInteractor.setTransitionState(idleTransitionState)
-            runCurrent()
             assertThat(alpha).isEqualTo(0f)
 
             // While state is GLANCEABLE_HUB, verify alpha is restored to full if glanceable hub is
@@ -293,6 +284,50 @@
         }
 
     @Test
+    fun glanceableHubAlpha_dreamToHub() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.glanceableHubAlpha)
+
+            // Start on dream
+            showDream()
+            assertThat(alpha).isEqualTo(1f)
+
+            // Start transitioning to glanceable hub
+            val progress = 0.6f
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    value = 0f,
+                )
+            )
+            runCurrent()
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    value = progress,
+                )
+            )
+            runCurrent()
+            // Keep notifications hidden during the transition from dream to hub
+            assertThat(alpha).isEqualTo(0)
+
+            // Finish transition to glanceable hub
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    value = 1f,
+                )
+            )
+            assertThat(alpha).isEqualTo(0f)
+        }
+
+    @Test
     fun validateMarginTop() =
         testScope.runTest {
             overrideResource(R.bool.config_use_large_screen_shade_header, false)
@@ -391,12 +426,11 @@
             assertThat(isOnGlanceableHubWithoutShade).isFalse()
 
             // Move to glanceable hub
-            val idleTransitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(CommunalScenes.Communal)
-                )
-            communalInteractor.setTransitionState(idleTransitionState)
-            runCurrent()
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GLANCEABLE_HUB,
+                testScope = this
+            )
             assertThat(isOnGlanceableHubWithoutShade).isTrue()
 
             // While state is GLANCEABLE_HUB, validate variations of both shade and qs expansion
@@ -726,6 +760,19 @@
         )
     }
 
+    private suspend fun TestScope.showDream() {
+        shadeRepository.setLockscreenShadeExpansion(0f)
+        shadeRepository.setQsExpansion(0f)
+        runCurrent()
+        keyguardRepository.setDreaming(true)
+        runCurrent()
+        keyguardTransitionRepository.sendTransitionSteps(
+            from = KeyguardState.LOCKSCREEN,
+            to = KeyguardState.DREAMING,
+            testScope,
+        )
+    }
+
     private suspend fun TestScope.showLockscreenWithShadeExpanded() {
         shadeRepository.setLockscreenShadeExpansion(1f)
         shadeRepository.setQsExpansion(0f)
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 517f88b..25596cc 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1135,6 +1135,14 @@
     <string name="hub_mode_add_widget_button_text">Add widget</string>
     <!-- Text for the button that exits the hub mode editing mode. [CHAR LIMIT=50] -->
     <string name="hub_mode_editing_exit_button_text">Done</string>
+    <!-- Title for the dialog that redirects users to change allowed widget category in settings. [CHAR LIMIT=NONE] -->
+    <string name="dialog_title_to_allow_any_widget">Allow any widget on lock screen?</string>
+    <!-- Text for the button in the dialog that opens when tapping on disabled widgets. [CHAR LIMIT=NONE] -->
+    <string name="button_text_to_open_settings">Open settings</string>
+    <!-- Title of a dialog. This text is confirming that the user wants to turn on access to their work apps, which the user had previously paused. "Work" is an adjective. [CHAR LIMIT=30] -->
+    <string name="work_mode_off_title">Unpause work apps?</string>
+    <!-- Title for button to unpause on work profile. [CHAR LIMIT=NONE] -->
+    <string name="work_mode_turn_on">Unpause</string>
 
     <!-- Related to user switcher --><skip/>
 
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 6e611fe..42ba05c 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -35,6 +35,9 @@
     srcs: [
         ":statslog-SystemUI-java-gen",
     ],
+    libs: [
+        "androidx.annotation_annotation",
+    ],
 }
 
 android_library {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
index aab0b1e..e88aaf01 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
@@ -22,6 +22,7 @@
 import android.content.ContentProvider
 import android.content.Context
 import android.content.Intent
+import android.util.Log
 import androidx.core.app.AppComponentFactory
 import com.android.systemui.dagger.ContextComponentHelper
 import com.android.systemui.dagger.SysUIComponent
@@ -90,8 +91,7 @@
         return app
     }
 
-    @UsesReflection(
-            KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject"))
+    @UsesReflection(KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject"))
     override fun instantiateProviderCompat(cl: ClassLoader, className: String): ContentProvider {
         val contentProvider = super.instantiateProviderCompat(cl, className)
         if (contentProvider is ContextInitializer) {
@@ -103,12 +103,11 @@
                         .getMethod("inject", contentProvider.javaClass)
                     injectMethod.invoke(rootComponent, contentProvider)
                 } catch (e: NoSuchMethodException) {
-                    throw RuntimeException("No injector for class: " + contentProvider.javaClass, e)
+                    Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
                 } catch (e: IllegalAccessException) {
-                    throw RuntimeException("Injector inaccessible for class: " +
-                            contentProvider.javaClass, e)
+                    Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
                 } catch (e: InvocationTargetException) {
-                    throw RuntimeException("Error while injecting: " + contentProvider.javaClass, e)
+                    Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
                 }
                 initializer
             }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
new file mode 100644
index 0000000..9e7fb4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal
+
+import android.annotation.SuppressLint
+import android.app.DreamManager
+import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.communalHub
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * A [CoreStartable] responsible for automatically starting the dream when the communal hub is
+ * shown, to support the user swiping away the hub to enter the dream.
+ */
+@SysUISingleton
+class CommunalDreamStartable
+@Inject
+constructor(
+    private val powerInteractor: PowerInteractor,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val dreamManager: DreamManager,
+    @Background private val bgScope: CoroutineScope,
+) : CoreStartable {
+    @SuppressLint("MissingPermission")
+    override fun start() {
+        if (!communalHub()) {
+            return
+        }
+
+        // Restart the dream underneath the hub in order to support the ability to swipe
+        // away the hub to enter the dream.
+        keyguardTransitionInteractor.finishedKeyguardState
+            .sample(powerInteractor.isAwake, keyguardInteractor.isDreaming)
+            .onEach { (finishedState, isAwake, dreaming) ->
+                if (
+                    finishedState == KeyguardState.GLANCEABLE_HUB &&
+                        !dreaming &&
+                        dreamManager.canStartDreaming(isAwake)
+                ) {
+                    dreamManager.startDream()
+                }
+            }
+            .launchIn(bgScope)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 8142957..940b48c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -18,9 +18,14 @@
 
 import android.app.smartspace.SmartspaceTarget
 import android.content.ComponentName
+import android.content.Intent
+import android.content.IntentFilter
 import android.os.UserHandle
+import android.os.UserManager
+import android.provider.Settings
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.communal.data.repository.CommunalMediaRepository
 import com.android.systemui.communal.data.repository.CommunalPrefsRepository
 import com.android.systemui.communal.data.repository.CommunalRepository
@@ -45,6 +50,7 @@
 import com.android.systemui.log.dagger.CommunalTableLog
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
@@ -53,6 +59,7 @@
 import com.android.systemui.util.kotlin.BooleanFlowOperators.and
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -78,6 +85,7 @@
 @Inject
 constructor(
     @Application applicationScope: CoroutineScope,
+    broadcastDispatcher: BroadcastDispatcher,
     private val communalRepository: CommunalRepository,
     private val widgetRepository: CommunalWidgetRepository,
     private val communalPrefsRepository: CommunalPrefsRepository,
@@ -88,6 +96,8 @@
     private val appWidgetHost: CommunalAppWidgetHost,
     private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
     private val userTracker: UserTracker,
+    private val activityStarter: ActivityStarter,
+    private val userManager: UserManager,
     sceneInteractor: SceneInteractor,
     sceneContainerFlags: SceneContainerFlags,
     @CommunalLog logBuffer: LogBuffer,
@@ -247,6 +257,18 @@
         editWidgetsActivityStarter.startActivity(preselectedKey)
     }
 
+    /**
+     * Navigates to communal widget setting after user has unlocked the device. Currently, this
+     * setting resides within the Hub Mode settings screen.
+     */
+    fun navigateToCommunalWidgetSettings() {
+        activityStarter.postStartActivityDismissingKeyguard(
+            Intent(Settings.ACTION_COMMUNAL_SETTING)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP),
+            /* delay= */ 0,
+        )
+    }
+
     /** Dismiss the CTA tile from the hub in view mode. */
     suspend fun dismissCtaTile() = communalPrefsRepository.setCtaDismissedForCurrentUser()
 
@@ -272,6 +294,33 @@
     fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) =
         widgetRepository.updateWidgetOrder(widgetIdToPriorityMap)
 
+    /** Request to unpause work profile that is currently in quiet mode. */
+    fun unpauseWorkProfile() {
+        userTracker.userProfiles
+            .find { it.isManagedProfile }
+            ?.userHandle
+            ?.let { userHandle ->
+                userManager.requestQuietModeEnabled(/* enableQuietMode */ false, userHandle)
+            }
+    }
+
+    /** Returns true if work profile is in quiet mode (disabled) for user handle. */
+    private fun isQuietModeEnabled(userHandle: UserHandle): Boolean =
+        userManager.isManagedProfile(userHandle.identifier) &&
+            userManager.isQuietModeEnabled(userHandle)
+
+    /** Emits whenever a work profile pause or unpause broadcast is received. */
+    private val updateOnWorkProfileBroadcastReceived: Flow<Unit> =
+        broadcastDispatcher
+            .broadcastFlow(
+                filter =
+                    IntentFilter().apply {
+                        addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+                        addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
+                    },
+            )
+            .emitOnStart()
+
     /** All widgets present in db. */
     val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
         isCommunalAvailable.flatMapLatest { available ->
@@ -282,8 +331,9 @@
     val widgetContent: Flow<List<WidgetContent>> =
         combine(
             widgetRepository.communalWidgets.map { filterWidgetsByExistingUsers(it) },
-            communalSettingsInteractor.communalWidgetCategories
-        ) { widgets, allowedCategories ->
+            communalSettingsInteractor.communalWidgetCategories,
+            updateOnWorkProfileBroadcastReceived,
+        ) { widgets, allowedCategories, _ ->
             widgets.map { widget ->
                 if (widget.providerInfo.widgetCategory and allowedCategories != 0) {
                     // At least one category this widget specified is allowed, so show it
@@ -291,6 +341,7 @@
                         appWidgetId = widget.appWidgetId,
                         providerInfo = widget.providerInfo,
                         appWidgetHost = appWidgetHost,
+                        inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
                     )
                 } else {
                     WidgetContent.DisabledWidget(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 12576d4..5fabd3c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -51,6 +51,7 @@
             override val appWidgetId: Int,
             override val providerInfo: AppWidgetProviderInfo,
             val appWidgetHost: CommunalAppWidgetHost,
+            val inQuietMode: Boolean,
         ) : WidgetContent {
             override val key = KEY.widget(appWidgetId)
             // Widget size is always half.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 35372cd..85f3c20 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -70,6 +70,10 @@
         communalInteractor.addWidget(componentName, user, priority, configurator)
     }
 
+    open fun onOpenEnableWidgetDialog() {}
+
+    open fun onOpenEnableWorkProfileDialog() {}
+
     /** A list of all the communal content to be displayed in the communal hub. */
     abstract val communalContent: Flow<List<CommunalContentModel>>
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 35b27aa..6e69ed7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -87,6 +87,14 @@
     override val isPopupOnDismissCtaShowing: Flow<Boolean> =
         _isPopupOnDismissCtaShowing.asStateFlow()
 
+    private val _isEnableWidgetDialogShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    val isEnableWidgetDialogShowing: Flow<Boolean> = _isEnableWidgetDialogShowing.asStateFlow()
+
+    private val _isEnableWorkProfileDialogShowing: MutableStateFlow<Boolean> =
+        MutableStateFlow(false)
+    val isEnableWorkProfileDialogShowing: Flow<Boolean> =
+        _isEnableWorkProfileDialogShowing.asStateFlow()
+
     /** Whether touches should be disabled in communal */
     val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
 
@@ -120,6 +128,40 @@
         setPopupOnDismissCtaVisibility(false)
     }
 
+    override fun onOpenEnableWidgetDialog() {
+        setIsEnableWidgetDialogShowing(true)
+    }
+
+    fun onEnableWidgetDialogConfirm() {
+        communalInteractor.navigateToCommunalWidgetSettings()
+        setIsEnableWidgetDialogShowing(false)
+    }
+
+    fun onEnableWidgetDialogCancel() {
+        setIsEnableWidgetDialogShowing(false)
+    }
+
+    override fun onOpenEnableWorkProfileDialog() {
+        setIsEnableWorkProfileDialogShowing(true)
+    }
+
+    fun onEnableWorkProfileDialogConfirm() {
+        communalInteractor.unpauseWorkProfile()
+        setIsEnableWorkProfileDialogShowing(false)
+    }
+
+    fun onEnableWorkProfileDialogCancel() {
+        setIsEnableWorkProfileDialogShowing(false)
+    }
+
+    private fun setIsEnableWidgetDialogShowing(isVisible: Boolean) {
+        _isEnableWidgetDialogShowing.value = isVisible
+    }
+
+    private fun setIsEnableWorkProfileDialogShowing(isVisible: Boolean) {
+        _isEnableWorkProfileDialogShowing.value = isVisible
+    }
+
     private fun setPopupOnDismissCtaVisibility(isVisible: Boolean) {
         _isPopupOnDismissCtaShowing.value = isVisible
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index a3d6ad4..21ee5bd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.back.domain.interactor.BackActionInteractor
 import com.android.systemui.biometrics.BiometricNotificationService
 import com.android.systemui.clipboardoverlay.ClipboardListener
+import com.android.systemui.communal.CommunalDreamStartable
 import com.android.systemui.communal.CommunalSceneStartable
 import com.android.systemui.communal.log.CommunalLoggerStartable
 import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
@@ -328,6 +329,11 @@
 
     @Binds
     @IntoMap
+    @ClassKey(CommunalDreamStartable::class)
+    abstract fun bindCommunalDreamStartable(impl: CommunalDreamStartable): CoreStartable
+
+    @Binds
+    @IntoMap
     @ClassKey(CommunalAppWidgetHostStartable::class)
     abstract fun bindCommunalAppWidgetHostStartable(
         impl: CommunalAppWidgetHostStartable
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index b97bace..f860893 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -33,7 +33,7 @@
 import com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP
 import com.android.systemui.complication.ComplicationLayoutParams.Position
 import com.android.systemui.dreams.dagger.DreamOverlayModule
-import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
@@ -53,7 +53,7 @@
     private val mStatusBarViewController: DreamOverlayStatusBarViewController,
     private val mOverlayStateController: DreamOverlayStateController,
     @Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
-    private val dreamOverlayViewModel: DreamOverlayViewModel,
+    private val dreamViewModel: DreamViewModel,
     @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
     private val mDreamInBlurAnimDurationMs: Long,
     @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
@@ -87,7 +87,7 @@
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
                 launch {
-                    dreamOverlayViewModel.dreamOverlayTranslationY.collect { px ->
+                    dreamViewModel.dreamOverlayTranslationY.collect { px ->
                         ComplicationLayoutParams.iteratePositions(
                             { position: Int -> setElementsTranslationYAtPosition(px, position) },
                             POSITION_TOP or POSITION_BOTTOM
@@ -96,7 +96,7 @@
                 }
 
                 launch {
-                    dreamOverlayViewModel.dreamOverlayTranslationX.collect { px ->
+                    dreamViewModel.dreamOverlayTranslationX.collect { px ->
                         ComplicationLayoutParams.iteratePositions(
                             { position: Int -> setElementsTranslationXAtPosition(px, position) },
                             POSITION_TOP or POSITION_BOTTOM
@@ -105,7 +105,7 @@
                 }
 
                 launch {
-                    dreamOverlayViewModel.dreamOverlayAlpha.collect { alpha ->
+                    dreamViewModel.dreamOverlayAlpha.collect { alpha ->
                         ComplicationLayoutParams.iteratePositions(
                             { position: Int ->
                                 setElementsAlphaAtPosition(
@@ -120,7 +120,7 @@
                 }
 
                 launch {
-                    dreamOverlayViewModel.transitionEnded.collect { _ ->
+                    dreamViewModel.transitionEnded.collect { _ ->
                         mOverlayStateController.setExitAnimationsRunning(false)
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt
deleted file mode 100644
index bd99f4b..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.ui.viewmodel
-
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
-import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
-import com.android.systemui.res.R
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.merge
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class DreamOverlayViewModel
-@Inject
-constructor(
-    configurationInteractor: ConfigurationInteractor,
-    toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
-    fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
-    private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
-) {
-
-    val dreamOverlayTranslationX: Flow<Float> =
-        merge(
-            toGlanceableHubTransitionViewModel.dreamOverlayTranslationX,
-            fromGlanceableHubTransitionInteractor.dreamOverlayTranslationX,
-        )
-
-    val dreamOverlayTranslationY: Flow<Float> =
-        configurationInteractor
-            .dimensionPixelSize(R.dimen.dream_overlay_exit_y_offset)
-            .flatMapLatest { px: Int ->
-                toLockscreenTransitionViewModel.dreamOverlayTranslationY(px)
-            }
-
-    val dreamOverlayAlpha: Flow<Float> =
-        merge(
-            toLockscreenTransitionViewModel.dreamOverlayAlpha,
-            toGlanceableHubTransitionViewModel.dreamOverlayAlpha,
-            fromGlanceableHubTransitionInteractor.dreamOverlayAlpha,
-        )
-
-    val transitionEnded = toLockscreenTransitionViewModel.transitionEnded
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
new file mode 100644
index 0000000..0cb57fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.ui.viewmodel
+
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dock.DockManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
+import com.android.systemui.res.R
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.merge
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class DreamViewModel
+@Inject
+constructor(
+    configurationInteractor: ConfigurationInteractor,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
+    private val toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
+    private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+    private val dockManager: DockManager,
+    private val communalInteractor: CommunalInteractor,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val userTracker: UserTracker,
+) {
+
+    fun startTransitionFromDream() {
+        val showGlanceableHub =
+            dockManager.isDocked &&
+                communalInteractor.isCommunalEnabled.value &&
+                !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
+        if (showGlanceableHub) {
+            toGlanceableHubTransitionViewModel.startTransition()
+            communalInteractor.onSceneChanged(CommunalScenes.Communal)
+        } else {
+            toLockscreenTransitionViewModel.startTransition()
+        }
+    }
+
+    val dreamOverlayTranslationX: Flow<Float> =
+        merge(
+                toGlanceableHubTransitionViewModel.dreamOverlayTranslationX,
+                fromGlanceableHubTransitionInteractor.dreamOverlayTranslationX,
+            )
+            .distinctUntilChanged()
+
+    val dreamOverlayTranslationY: Flow<Float> =
+        configurationInteractor
+            .dimensionPixelSize(R.dimen.dream_overlay_exit_y_offset)
+            .flatMapLatest { px: Int ->
+                toLockscreenTransitionViewModel.dreamOverlayTranslationY(px)
+            }
+
+    val dreamAlpha: Flow<Float> =
+        merge(
+                toLockscreenTransitionViewModel.dreamOverlayAlpha,
+                toGlanceableHubTransitionViewModel.dreamAlpha,
+            )
+            .distinctUntilChanged()
+
+    val dreamOverlayAlpha: Flow<Float> =
+        merge(
+                toLockscreenTransitionViewModel.dreamOverlayAlpha,
+                toGlanceableHubTransitionViewModel.dreamOverlayAlpha,
+                fromGlanceableHubTransitionInteractor.dreamOverlayAlpha,
+            )
+            .distinctUntilChanged()
+
+    val transitionEnded =
+        keyguardTransitionInteractor.fromDreamingTransition.filter { step ->
+            step.transitionState == TransitionState.FINISHED ||
+                step.transitionState == TransitionState.CANCELED
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6bb84649..a199fea 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -76,23 +76,6 @@
     val NOTIFICATION_MEMORY_LOGGING_ENABLED =
             releasedFlag("notification_memory_logging_enabled")
 
-    // TODO(b/260335638): Tracking Bug
-    @JvmField
-    val NOTIFICATION_INLINE_REPLY_ANIMATION = releasedFlag("notification_inline_reply_animation")
-
-    // TODO(b/288326013): Tracking Bug
-    @JvmField
-    val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION =
-        unreleasedFlag("notification_async_hybrid_view_inflation", teamfood = false)
-
-    @JvmField
-    val ANIMATED_NOTIFICATION_SHADE_INSETS =
-        releasedFlag("animated_notification_shade_insets")
-
-    // TODO(b/268005230): Tracking Bug
-    @JvmField
-    val SENSITIVE_REVEAL_ANIM = releasedFlag("sensitive_reveal_anim")
-
     // TODO(b/280783617): Tracking Bug
     @Keep
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 106fdf1..5565ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.ComposeLockscreen
 import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
@@ -107,6 +108,7 @@
     private val lockscreenContentViewModel: LockscreenContentViewModel,
     private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>,
     private val keyguardBlueprintViewBinder: KeyguardBlueprintViewBinder,
+    private val clockInteractor: KeyguardClockInteractor,
 ) : CoreStartable {
 
     private var rootViewHandle: DisposableHandle? = null
@@ -220,7 +222,11 @@
             blueprints.mapNotNull { it as? ComposableLockscreenSceneBlueprint }.toSet()
         return ComposeView(context).apply {
             setContent {
-                LockscreenContent(viewModel = viewModel, blueprints = sceneBlueprints)
+                LockscreenContent(
+                        viewModel = viewModel,
+                        blueprints = sceneBlueprints,
+                        clockInteractor = clockInteractor
+                    )
                     .Content(modifier = Modifier.fillMaxSize())
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index a37397d..43a8b40 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -140,13 +140,13 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.SystemPropertiesHelper;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -1229,9 +1229,7 @@
 
                         if (isDream) {
                             initAlphaForAnimationTargets(wallpapers);
-                            getRemoteSurfaceAlphaApplier().accept(0.0f);
-                            mDreamingToLockscreenTransitionViewModel.get()
-                                    .startTransition();
+                            mDreamViewModel.get().startTransitionFromDream();
                             mUnoccludeFromDreamFinishedCallback = finishedCallback;
                             return;
                         }
@@ -1359,8 +1357,7 @@
     private final UiEventLogger mUiEventLogger;
     private final SessionTracker mSessionTracker;
     private final CoroutineDispatcher mMainDispatcher;
-    private final Lazy<DreamingToLockscreenTransitionViewModel>
-            mDreamingToLockscreenTransitionViewModel;
+    private final Lazy<DreamViewModel> mDreamViewModel;
     private RemoteAnimationTarget mRemoteAnimationTarget;
 
     private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
@@ -1409,7 +1406,7 @@
             SystemSettings systemSettings,
             SystemClock systemClock,
             @Main CoroutineDispatcher mainDispatcher,
-            Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
+            Lazy<DreamViewModel> dreamViewModel,
             SystemPropertiesHelper systemPropertiesHelper,
             Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
             SelectedUserInteractor selectedUserInteractor,
@@ -1480,7 +1477,7 @@
         mUiEventLogger = uiEventLogger;
         mSessionTracker = sessionTracker;
 
-        mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
+        mDreamViewModel = dreamViewModel;
         mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager;
         mMainDispatcher = mainDispatcher;
 
@@ -1611,9 +1608,8 @@
 
             ViewRootImpl viewRootImpl = mKeyguardViewControllerLazy.get().getViewRootImpl();
             if (viewRootImpl != null) {
-                DreamingToLockscreenTransitionViewModel viewModel =
-                        mDreamingToLockscreenTransitionViewModel.get();
-                collectFlow(viewRootImpl.getView(), viewModel.getDreamOverlayAlpha(),
+                final DreamViewModel viewModel = mDreamViewModel.get();
+                collectFlow(viewRootImpl.getView(), viewModel.getDreamAlpha(),
                         getRemoteSurfaceAlphaApplier(), mMainDispatcher);
                 collectFlow(viewRootImpl.getView(), viewModel.getTransitionEnded(),
                         getFinishedCallbackConsumer(), mMainDispatcher);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 5306645..a243b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -43,6 +43,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.SystemPropertiesHelper;
@@ -59,7 +60,6 @@
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule;
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.settings.UserTracker;
@@ -160,7 +160,7 @@
             SystemSettings systemSettings,
             SystemClock systemClock,
             @Main CoroutineDispatcher mainDispatcher,
-            Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
+            Lazy<DreamViewModel> dreamViewModel,
             SystemPropertiesHelper systemPropertiesHelper,
             Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
             SelectedUserInteractor selectedUserInteractor,
@@ -207,7 +207,7 @@
                 systemSettings,
                 systemClock,
                 mainDispatcher,
-                dreamingToLockscreenTransitionViewModel,
+                dreamViewModel,
                 systemPropertiesHelper,
                 wmLockscreenVisibilityManager,
                 selectedUserInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 3137138..9a6088d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
@@ -97,6 +98,18 @@
         }
     }
 
+    fun startToGlanceableHubTransition() {
+        scope.launch {
+            KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode()
+            if (
+                transitionInteractor.startedKeyguardState.replayCache.last() ==
+                    KeyguardState.DREAMING
+            ) {
+                startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+            }
+        }
+    }
+
     private fun listenForDreamingToOccluded() {
         if (KeyguardWmStateRefactor.isEnabled) {
             scope.launch {
@@ -205,14 +218,18 @@
         return ValueAnimator().apply {
             interpolator = Interpolators.LINEAR
             duration =
-                if (toState == KeyguardState.LOCKSCREEN) TO_LOCKSCREEN_DURATION.inWholeMilliseconds
-                else DEFAULT_DURATION.inWholeMilliseconds
+                when (toState) {
+                    KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+                    KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
+                    else -> DEFAULT_DURATION
+                }.inWholeMilliseconds
         }
     }
 
     companion object {
         const val TAG = "FromDreamingTransitionInteractor"
         private val DEFAULT_DURATION = 500.milliseconds
+        val TO_GLANCEABLE_HUB_DURATION = 1.seconds
         val TO_LOCKSCREEN_DURATION = 1167.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
index 7443010..197221a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalTransitionProgress
 import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -29,13 +28,10 @@
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.flowOn
 
 class GlanceableHubTransitions
 @Inject
 constructor(
-    @Background private val bgDispatcher: CoroutineDispatcher,
     private val transitionInteractor: KeyguardTransitionInteractor,
     private val transitionRepository: KeyguardTransitionRepository,
     private val communalInteractor: CommunalInteractor,
@@ -64,16 +60,16 @@
         communalInteractor
             .transitionProgressToScene(toScene)
             .sample(
-                transitionInteractor.startedKeyguardTransitionStep.flowOn(bgDispatcher),
+                transitionInteractor.startedKeyguardState,
                 ::Pair,
             )
-            .collect { (transitionProgress, lastStartedStep) ->
+            .collect { (transitionProgress, lastStartedState) ->
                 val id = transitionId
                 if (id == null) {
                     // No transition started.
                     if (
                         transitionProgress is CommunalTransitionProgress.Transition &&
-                            lastStartedStep.to == fromState
+                            lastStartedState == fromState
                     ) {
                         transitionId =
                             transitionRepository.startTransition(
@@ -86,7 +82,7 @@
                             )
                     }
                 } else {
-                    if (lastStartedStep.to != toState) {
+                    if (lastStartedState != toState) {
                         return@collect
                     }
                     // An existing `id` means a transition is started, and calls to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
index c64f277..789e4fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
@@ -19,6 +19,7 @@
 import com.android.app.animation.Interpolators.EMPHASIZED
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.res.R
@@ -36,7 +37,9 @@
 constructor(
     animationFlow: KeyguardTransitionAnimationFlow,
     configurationInteractor: ConfigurationInteractor,
+    private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
 ) {
+    fun startTransition() = fromDreamingTransitionInteractor.startToGlanceableHubTransition()
 
     private val transitionAnimation =
         animationFlow.setup(
@@ -58,6 +61,9 @@
                 )
             }
 
+    // Keep the dream visible while the hub swipes in over the dream.
+    val dreamAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f)
+
     val dreamOverlayAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
             duration = 167.milliseconds,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index d3277cd..f191aa7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -20,16 +20,13 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
 
 /**
  * Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -40,7 +37,6 @@
 class DreamingToLockscreenTransitionViewModel
 @Inject
 constructor(
-    keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
@@ -53,12 +49,6 @@
             to = KeyguardState.LOCKSCREEN,
         )
 
-    val transitionEnded =
-        keyguardTransitionInteractor.fromDreamingTransition.filter { step ->
-            step.transitionState == TransitionState.FINISHED ||
-                step.transitionState == TransitionState.CANCELED
-        }
-
     /** Dream overlay y-translation on exit */
     fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> {
         return transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index e5b5964..abf2372 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -79,6 +79,8 @@
 
     val notificationAlpha: Flow<Float> = keyguardAlpha
 
+    val shortcutsAlpha: Flow<Float> = keyguardAlpha
+
     val notificationTranslationX: Flow<Float> =
         keyguardTranslationX.map { it.value }.filterNotNull()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index f981fd5..58c45c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -36,6 +36,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 @SysUISingleton
@@ -67,7 +68,16 @@
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = LARGE
+                initialValue = LARGE,
+            )
+
+    val isLargeClockVisible =
+        clockSize
+            .map { it == LARGE }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
             )
 
     val currentClock = keyguardClockInteractor.currentClock
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 4db942cc..c4383fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -52,6 +52,7 @@
     occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
     offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel,
     primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel,
+    glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
     lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
     lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel,
     lockscreenToDreamingHostedTransitionViewModel: LockscreenToDreamingHostedTransitionViewModel,
@@ -59,6 +60,7 @@
     lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
     lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
     lockscreenToPrimaryBouncerTransitionViewModel: LockscreenToPrimaryBouncerTransitionViewModel,
+    lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
     transitionInteractor: KeyguardTransitionInteractor,
 ) {
 
@@ -110,6 +112,7 @@
             occludedToLockscreenTransitionViewModel.shortcutsAlpha,
             offToLockscreenTransitionViewModel.shortcutsAlpha,
             primaryBouncerToLockscreenTransitionViewModel.shortcutsAlpha,
+            glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha,
         )
 
     /** alpha while fading the quick affordances in */
@@ -122,6 +125,7 @@
             lockscreenToGoneTransitionViewModel.shortcutsAlpha,
             lockscreenToOccludedTransitionViewModel.shortcutsAlpha,
             lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha,
+            lockscreenToGlanceableHubTransitionViewModel.shortcutsAlpha,
             shadeExpansionAlpha,
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index d89523d..288ef3c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -69,8 +70,8 @@
 
     /** The key of the scene we should switch to when swiping down from the top edge. */
     val downFromTopEdgeDestinationSceneKey: StateFlow<SceneKey?> =
-        shadeInteractor.isSplitShade
-            .map { isSplitShade -> Scenes.QuickSettings.takeUnless { isSplitShade } }
+        shadeInteractor.shadeMode
+            .map { shadeMode -> Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single } }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index 978e71e..b7f7b06 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -81,6 +81,8 @@
 
     val notificationAlpha: Flow<Float> = keyguardAlpha
 
+    val shortcutsAlpha: Flow<Float> = keyguardAlpha
+
     val notificationTranslationX: Flow<Float> =
         keyguardTranslationX.map { it.value }.filterNotNull()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 55d7f8e..8a84496 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -19,10 +19,13 @@
 import android.app.NotificationManager
 import android.content.Context
 import android.content.Intent
+import android.content.pm.LauncherApps
 import android.content.res.Resources
 import android.net.Uri
 import android.os.Handler
 import android.os.UserHandle
+import android.util.Log
+import androidx.core.content.FileProvider
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dagger.qualifiers.LongRunning
 import com.android.systemui.dagger.qualifiers.Main
@@ -34,7 +37,12 @@
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
 import com.android.traceur.FileSender
 import com.android.traceur.TraceUtils
+import java.io.File
+import java.io.FileOutputStream
+import java.nio.file.Files
 import java.util.concurrent.Executor
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
 import javax.inject.Inject
 
 class IssueRecordingService
@@ -86,6 +94,10 @@
             }
             ACTION_STOP,
             ACTION_STOP_NOTIF -> {
+                // ViewCapture needs to save it's data before it is disabled, or else the data will
+                // be lost. This is expected to change in the near future, and when that happens
+                // this line should be removed.
+                getSystemService(LauncherApps::class.java)?.saveViewCaptureData()
                 TraceUtils.traceStop(contentResolver)
             }
             ACTION_SHARE -> {
@@ -102,21 +114,22 @@
     }
 
     private fun shareRecording(intent: Intent) {
-        val files = TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get()
-        val traceUris: MutableList<Uri> = FileSender.getUriForFiles(this, files, AUTHORITY)
-
-        if (
-            intent.hasExtra(EXTRA_PATH) && intent.getStringExtra(EXTRA_PATH)?.isNotEmpty() == true
-        ) {
-            traceUris.add(Uri.parse(intent.getStringExtra(EXTRA_PATH)))
-        }
-
+        val sharableUri: Uri =
+            zipAndPackageRecordings(
+                TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get(),
+                intent.getStringExtra(EXTRA_PATH)
+            )
+                ?: return
         val sendIntent =
-            FileSender.buildSendIntent(this, traceUris).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            FileSender.buildSendIntent(this, listOf(sharableUri))
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
 
         if (mNotificationId != NOTIF_BASE_ID) {
-            val currentUserId = mUserContextTracker.userContext.userId
-            mNotificationManager.cancelAsUser(null, mNotificationId, UserHandle(currentUserId))
+            mNotificationManager.cancelAsUser(
+                null,
+                mNotificationId,
+                UserHandle(mUserContextTracker.userContext.userId)
+            )
         }
 
         // TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
@@ -130,11 +143,39 @@
         )
     }
 
+    private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecordingUri: String?): Uri? {
+        try {
+            externalCacheDir?.mkdirs()
+            val outZip: File = File.createTempFile(TEMP_FILE_PREFIX, ZIP_SUFFIX, externalCacheDir)
+            ZipOutputStream(FileOutputStream(outZip)).use { os ->
+                traceFiles.forEach { file ->
+                    os.putNextEntry(ZipEntry(file.name))
+                    Files.copy(file.toPath(), os)
+                    os.closeEntry()
+                }
+                if (screenRecordingUri != null) {
+                    contentResolver.openInputStream(Uri.parse(screenRecordingUri))?.use {
+                        os.putNextEntry(ZipEntry(SCREEN_RECORDING_ZIP_LABEL))
+                        it.transferTo(os)
+                        os.closeEntry()
+                    }
+                }
+            }
+            return FileProvider.getUriForFile(this, AUTHORITY, outZip)
+        } catch (e: Exception) {
+            Log.e(TAG, "Failed to zip and package Recordings. Cannot share with BetterBug.", e)
+            return null
+        }
+    }
+
     companion object {
         private const val TAG = "IssueRecordingService"
         private const val CHANNEL_ID = "issue_record"
         private const val EXTRA_SCREEN_RECORD = "extra_screenRecord"
         private const val EXTRA_WINSCOPE_TRACING = "extra_winscopeTracing"
+        private const val ZIP_SUFFIX = ".zip"
+        private const val TEMP_FILE_PREFIX = "issue_recording"
+        private const val SCREEN_RECORDING_ZIP_LABEL = "screen-recording.mp4"
 
         private val DEFAULT_TRACE_TAGS = listOf<String>()
         private const val DEFAULT_BUFFER_SIZE = 16384
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
index a490fe2..451fd67 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
@@ -40,17 +41,17 @@
     shadeInteractor: ShadeInteractor,
 ) {
     val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
-        shadeInteractor.isSplitShade
-            .map { isSplitShade -> destinationScenes(isSplitShade = isSplitShade) }
+        shadeInteractor.shadeMode
+            .map { shadeMode -> destinationScenes(shadeMode = shadeMode) }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = destinationScenes(isSplitShade = shadeInteractor.isSplitShade.value)
+                initialValue = destinationScenes(shadeMode = shadeInteractor.shadeMode.value)
             )
 
-    private fun destinationScenes(isSplitShade: Boolean): Map<UserAction, UserActionResult> {
+    private fun destinationScenes(shadeMode: ShadeMode): Map<UserAction, UserActionResult> {
         return buildMap {
-            if (!isSplitShade) {
+            if (shadeMode == ShadeMode.Single) {
                 this[
                     Swipe(
                         pointerCount = 2,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
index d8c3850..f01e9be 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -24,6 +24,7 @@
 import android.graphics.drawable.Drawable
 import android.util.Log
 import android.view.Display
+import android.view.KeyEvent
 import android.view.LayoutInflater
 import android.view.ScrollCaptureResponse
 import android.view.View
@@ -34,12 +35,15 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.res.R
+import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
 
 /**
  * Legacy implementation of screenshot view methods. Just proxies the calls down into the original
  * ScreenshotView.
  */
-class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy {
+class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLogger) :
+    ScreenshotViewProxy {
     override val view: ScreenshotView =
         LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView
     override val screenshotPreview: View
@@ -52,13 +56,6 @@
         set(value) {
             view.setDefaultTimeoutMillis(value)
         }
-    override var onBackInvokedCallback: OnBackInvokedCallback = OnBackInvokedCallback {
-        Log.wtf(TAG, "OnBackInvoked called before being set!")
-    }
-    override var onKeyListener: View.OnKeyListener? = null
-        set(value) {
-            view.setOnKeyListener(value)
-        }
     override var flags: FeatureFlags? = null
         set(value) {
             view.setFlags(value)
@@ -67,10 +64,6 @@
         set(value) {
             view.setPackageName(value)
         }
-    override var logger: UiEventLogger? = null
-        set(value) {
-            view.setUiEventLogger(value)
-        }
     override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
         set(value) {
             view.setCallbacks(value)
@@ -88,31 +81,9 @@
         get() = view.isPendingSharedTransition
 
     init {
-
-        view.addOnAttachStateChangeListener(
-            object : View.OnAttachStateChangeListener {
-                override fun onViewAttachedToWindow(view: View) {
-                    if (LogConfig.DEBUG_INPUT) {
-                        Log.d(TAG, "Registering Predictive Back callback")
-                    }
-                    view
-                        .findOnBackInvokedDispatcher()
-                        ?.registerOnBackInvokedCallback(
-                            OnBackInvokedDispatcher.PRIORITY_DEFAULT,
-                            onBackInvokedCallback
-                        )
-                }
-
-                override fun onViewDetachedFromWindow(view: View) {
-                    if (LogConfig.DEBUG_INPUT) {
-                        Log.d(TAG, "Unregistering Predictive Back callback")
-                    }
-                    view
-                        .findOnBackInvokedDispatcher()
-                        ?.unregisterOnBackInvokedCallback(onBackInvokedCallback)
-                }
-            }
-        )
+        view.setUiEventLogger(logger)
+        addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
+        setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
         if (LogConfig.DEBUG_WINDOW) {
             Log.d(TAG, "adding OnComputeInternalInsetsListener")
         }
@@ -135,7 +106,20 @@
     override fun setChipIntents(imageData: ScreenshotController.SavedImageData) =
         view.setChipIntents(imageData)
 
-    override fun animateDismissal() = view.animateDismissal()
+    override fun requestDismissal(event: ScreenshotEvent) {
+        if (DEBUG_DISMISS) {
+            Log.d(TAG, "screenshot dismissal requested")
+        }
+        // If we're already animating out, don't restart the animation
+        if (view.isDismissing) {
+            if (DEBUG_DISMISS) {
+                Log.v(TAG, "Already dismissing, ignoring duplicate command $event")
+            }
+            return
+        }
+        logger.log(event, 0, packageName)
+        view.animateDismissal()
+    }
 
     override fun showScrollChip(packageName: String, onClick: Runnable) =
         view.showScrollChip(packageName, onClick)
@@ -177,9 +161,58 @@
         view.post(runnable)
     }
 
+    private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
+        val onBackInvokedCallback = OnBackInvokedCallback {
+            if (LogConfig.DEBUG_INPUT) {
+                Log.d(TAG, "Predictive Back callback dispatched")
+            }
+            onDismissRequested.invoke(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+        }
+        view.addOnAttachStateChangeListener(
+            object : View.OnAttachStateChangeListener {
+                override fun onViewAttachedToWindow(v: View) {
+                    if (LogConfig.DEBUG_INPUT) {
+                        Log.d(TAG, "Registering Predictive Back callback")
+                    }
+                    view
+                        .findOnBackInvokedDispatcher()
+                        ?.registerOnBackInvokedCallback(
+                            OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                            onBackInvokedCallback
+                        )
+                }
+
+                override fun onViewDetachedFromWindow(view: View) {
+                    if (LogConfig.DEBUG_INPUT) {
+                        Log.d(TAG, "Unregistering Predictive Back callback")
+                    }
+                    view
+                        .findOnBackInvokedDispatcher()
+                        ?.unregisterOnBackInvokedCallback(onBackInvokedCallback)
+                }
+            }
+        )
+    }
+    private fun setOnKeyListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
+        view.setOnKeyListener(
+            object : View.OnKeyListener {
+                override fun onKey(view: View, keyCode: Int, event: KeyEvent): Boolean {
+                    if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
+                        if (LogConfig.DEBUG_INPUT) {
+                            Log.d(TAG, "onKeyEvent: $keyCode")
+                        }
+                        onDismissRequested.invoke(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+                        return true
+                    }
+                    return false
+                }
+            }
+        )
+    }
+
     class Factory : ScreenshotViewProxy.Factory {
-        override fun getProxy(context: Context): ScreenshotViewProxy {
-            return LegacyScreenshotViewProxy(context)
+        override fun getProxy(context: Context, logger: UiEventLogger): ScreenshotViewProxy {
+            return LegacyScreenshotViewProxy(context, logger)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 1ca9b98..6bab956 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -21,7 +21,6 @@
 
 import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
@@ -64,7 +63,6 @@
 import android.view.Display;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
-import android.view.KeyEvent;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationTarget;
 import android.view.ScrollCaptureResponse;
@@ -333,12 +331,7 @@
 
         mScreenshotHandler = timeoutHandler;
         mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
-        mScreenshotHandler.setOnTimeoutRunnable(() -> {
-            if (DEBUG_UI) {
-                Log.d(TAG, "Corner timeout hit");
-            }
-            dismissScreenshot(SCREENSHOT_INTERACTION_TIMEOUT);
-        });
+
 
         mDisplayId = displayId;
         mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
@@ -351,7 +344,14 @@
         mMessageContainerController = messageContainerController;
         mAssistContentRequester = assistContentRequester;
 
-        mViewProxy = viewProxyFactory.getProxy(mContext);
+        mViewProxy = viewProxyFactory.getProxy(mContext, mUiEventLogger);
+
+        mScreenshotHandler.setOnTimeoutRunnable(() -> {
+            if (DEBUG_UI) {
+                Log.d(TAG, "Corner timeout hit");
+            }
+            mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT);
+        });
 
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
 
@@ -376,7 +376,7 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) {
-                    dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+                    mViewProxy.requestDismissal(SCREENSHOT_DISMISSED_OTHER);
                 }
             }
         };
@@ -525,22 +525,11 @@
     }
 
     /**
-     * Clears current screenshot
+     * Requests the view to dismiss the current screenshot (may be ignored, if screenshot is already
+     * being dismissed)
      */
-    void dismissScreenshot(ScreenshotEvent event) {
-        if (DEBUG_DISMISS) {
-            Log.d(TAG, "dismissScreenshot");
-        }
-        // If we're already animating out, don't restart the animation
-        if (mViewProxy.isDismissing()) {
-            if (DEBUG_DISMISS) {
-                Log.v(TAG, "Already dismissing, ignoring duplicate command");
-            }
-            return;
-        }
-        mUiEventLogger.log(event, 0, mPackageName);
-        mScreenshotHandler.cancelTimeout();
-        mViewProxy.animateDismissal();
+    void requestDismissal(ScreenshotEvent event) {
+        mViewProxy.requestDismissal(event);
     }
 
     boolean isPendingSharedTransition() {
@@ -572,10 +561,6 @@
         mScreenshotSoundController.releaseScreenshotSoundAsync();
     }
 
-    private void respondToKeyDismissal() {
-        dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
-    }
-
     /**
      * Update resources on configuration change. Reinflate for theme/color changes.
      */
@@ -585,13 +570,6 @@
         }
 
         mMessageContainerController.setView(mViewProxy.getView());
-        mViewProxy.setLogger(mUiEventLogger);
-        mViewProxy.setOnBackInvokedCallback(() -> {
-            if (DEBUG_INPUT) {
-                Log.d(TAG, "Predictive Back callback dispatched");
-            }
-            respondToKeyDismissal();
-        });
         mViewProxy.setCallbacks(new ScreenshotView.ScreenshotViewCallback() {
             @Override
             public void onUserInteraction() {
@@ -622,17 +600,6 @@
         mViewProxy.setDefaultDisplay(mDisplayId);
         mViewProxy.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
 
-        mViewProxy.setOnKeyListener((v, keyCode, event) -> {
-            if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
-                if (DEBUG_INPUT) {
-                    Log.d(TAG, "onKeyEvent: " + keyCode);
-                }
-                respondToKeyDismissal();
-                return true;
-            }
-            return false;
-        });
-
         if (DEBUG_WINDOW) {
             Log.d(TAG, "setContentView: " + mViewProxy.getView());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
index 381404a..d5c7f95 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -24,11 +24,9 @@
 import android.graphics.drawable.Drawable
 import android.view.ScrollCaptureResponse
 import android.view.View
-import android.view.View.OnKeyListener
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
 import android.view.WindowInsets
-import android.window.OnBackInvokedCallback
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.flags.FeatureFlags
 
@@ -39,11 +37,8 @@
 
     var defaultDisplay: Int
     var defaultTimeoutMillis: Long
-    var onBackInvokedCallback: OnBackInvokedCallback
-    var onKeyListener: OnKeyListener?
     var flags: FeatureFlags?
     var packageName: String
-    var logger: UiEventLogger?
     var callbacks: ScreenshotView.ScreenshotViewCallback?
     var screenshot: ScreenshotData?
 
@@ -58,7 +53,7 @@
     fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
     fun addQuickShareChip(quickShareAction: Notification.Action)
     fun setChipIntents(imageData: ScreenshotController.SavedImageData)
-    fun animateDismissal()
+    fun requestDismissal(event: ScreenshotEvent)
 
     fun showScrollChip(packageName: String, onClick: Runnable)
     fun hideScrollChip()
@@ -82,6 +77,6 @@
     fun post(runnable: Runnable)
 
     interface Factory {
-        fun getProxy(context: Context): ScreenshotViewProxy
+        fun getProxy(context: Context, logger: UiEventLogger): ScreenshotViewProxy
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index e464fd0..bc33755 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -136,7 +136,7 @@
     fun onCloseSystemDialogsReceived() {
         screenshotControllers.forEach { (_, screenshotController) ->
             if (!screenshotController.isPendingSharedTransition) {
-                screenshotController.dismissScreenshot(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+                screenshotController.requestDismissal(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 0991c9a..9cf347b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -53,9 +53,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.ScreenshotRequest;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
 
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -89,7 +89,7 @@
                     // TODO(b/295143676): move receiver inside executor when the flag is enabled.
                     mTakeScreenshotExecutor.get().onCloseSystemDialogsReceived();
                 } else if (!mScreenshot.isPendingSharedTransition()) {
-                    mScreenshot.dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+                    mScreenshot.requestDismissal(SCREENSHOT_DISMISSED_OTHER);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index e92630f..92d6ec97 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -63,7 +63,7 @@
 
 public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
     private static final String TAG = "CentralSurfaces.BrightnessController";
-    private static final int SLIDER_ANIMATION_DURATION = 1000;
+    private static final int SLIDER_ANIMATION_DURATION = 3000;
 
     private static final int MSG_UPDATE_SLIDER = 1;
     private static final int MSG_ATTACH_LISTENER = 2;
@@ -96,6 +96,7 @@
     };
 
     private volatile boolean mAutomatic;  // Brightness adjusted automatically using ambient light.
+    private boolean mTrackingTouch = false; // Brightness adjusted via touch events.
     private volatile boolean mIsVrModeEnabled;
     private boolean mListening;
     private boolean mExternalChange;
@@ -330,6 +331,7 @@
 
     @Override
     public void onChanged(boolean tracking, int value, boolean stopTracking) {
+        mTrackingTouch = tracking;
         if (mExternalChange) return;
 
         if (mSliderAnimator != null) {
@@ -396,6 +398,12 @@
         }
     }
 
+    private boolean triggeredByBrightnessKey() {
+        // When the brightness mode is manual and the user isn't changing the brightness via the
+        // brightness slider, assume changes are coming from a brightness key.
+        return !mAutomatic && !mTrackingTouch;
+    }
+
     private void updateSlider(float brightnessValue, boolean inVrMode) {
         final float min = mBrightnessMin;
         final float max = mBrightnessMax;
@@ -417,12 +425,13 @@
     }
 
     private void animateSliderTo(int target) {
-        if (!mControlValueInitialized || !mControl.isVisible()) {
+        if (!mControlValueInitialized || !mControl.isVisible() || triggeredByBrightnessKey()) {
             // Don't animate the first value since its default state isn't meaningful to users.
             // We also don't want to animate slider if it's not visible - especially important when
             // two sliders are active at the same time in split shade (one in QS and one in QQS),
             // as this negatively affects transition between them and they share mirror slider -
-            // animating it from two different sources causes janky motion
+            // animating it from two different sources causes janky motion.
+            // Don't animate if the value is changed via the brightness keys of a keyboard.
             mControl.setValue(target);
             mControlValueInitialized = true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index d3869ba..3169e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import com.android.systemui.util.kotlin.collectFlow
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -47,6 +48,7 @@
 constructor(
     private val communalInteractor: CommunalInteractor,
     private val communalViewModel: CommunalViewModel,
+    private val dialogFactory: SystemUIDialogFactory,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val shadeInteractor: ShadeInteractor,
     private val powerManager: PowerManager,
@@ -119,7 +121,14 @@
     ): View {
         return initView(
             ComposeView(context).apply {
-                setContent { PlatformTheme { CommunalContainer(viewModel = communalViewModel) } }
+                setContent {
+                    PlatformTheme {
+                        CommunalContainer(
+                            viewModel = communalViewModel,
+                            dialogFactory = dialogFactory,
+                        )
+                    }
+                }
             }
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index cdb520f..b867550 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1540,13 +1540,13 @@
         mKeyguardBottomArea = keyguardBottomArea;
     }
 
-    @Override
+    /** Sets a listener to be notified when the shade starts opening or finishes closing. */
     public void setOpenCloseListener(OpenCloseListener openCloseListener) {
         SceneContainerFlag.assertInLegacyMode();
         mOpenCloseListener = openCloseListener;
     }
 
-    @Override
+    /** Sets a listener to be notified when touch tracking begins. */
     public void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) {
         mTrackingStartedListener = trackingStartedListener;
     }
@@ -2603,7 +2603,6 @@
         return maxHeight;
     }
 
-    @Override
     public boolean isExpandingOrCollapsing() {
         float lockscreenExpansionProgress = mQsController.getLockscreenShadeDragProgress();
         return mIsExpandingOrCollapsing
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt b/packages/SystemUI/src/com/android/systemui/shade/OpenCloseListener.kt
similarity index 66%
rename from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
rename to packages/SystemUI/src/com/android/systemui/shade/OpenCloseListener.kt
index 98a9e93..108dd478 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/OpenCloseListener.kt
@@ -14,16 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.ui.screens
+package com.android.systemui.shade
 
-import androidx.activity.result.IntentSenderRequest
+/** Listens for when shade begins opening or finishes closing. */
+interface OpenCloseListener {
+    /** Called when the shade finishes closing. */
+    fun onClosingFinished()
 
-sealed class UiState {
-    data object CredentialScreen : UiState()
-
-    data class CredentialSelected(
-        val intentSenderRequest: IntentSenderRequest?
-    ) : UiState()
-
-    data object Cancel : UiState()
+    /** Called when the shade starts opening. */
+    fun onOpenStarted()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 27168a7..177c3db 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -89,8 +89,7 @@
 
     override fun isShadeFullyOpen(): Boolean = shadeInteractor.isAnyFullyExpanded.value
 
-    override fun isExpandingOrCollapsing(): Boolean =
-        shadeInteractor.anyExpansion.value > 0f && shadeInteractor.anyExpansion.value < 1f
+    override fun isExpandingOrCollapsing(): Boolean = shadeInteractor.isUserInteracting.value
 
     override fun instantExpandShade() {
         // Do nothing
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt
index a9ba6f9..859fce5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt
@@ -27,9 +27,6 @@
      */
     @Deprecated("Use ShadeInteractor instead") fun expandToNotifications()
 
-    /** Returns whether the shade is expanding or collapsing itself or quick settings. */
-    val isExpandingOrCollapsing: Boolean
-
     /**
      * Returns whether the shade height is greater than zero (i.e. partially or fully expanded),
      * there is a HUN, the shade is animating, or the shade is instantly expanding.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 07236d1..de21a73 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -31,9 +31,6 @@
  * @see NotificationPanelViewController
  */
 interface ShadeViewController {
-    /** Returns whether the shade is expanding or collapsing itself or quick settings. */
-    val isExpandingOrCollapsing: Boolean
-
     /**
      * Returns whether the shade height is greater than zero or the shade is expecting a synthesized
      * down event.
@@ -52,15 +49,9 @@
     /** Returns whether the shade's top level view is enabled. */
     val isViewEnabled: Boolean
 
-    /** Sets a listener to be notified when the shade starts opening or finishes closing. */
-    fun setOpenCloseListener(openCloseListener: OpenCloseListener)
-
     /** Returns whether status bar icons should be hidden when the shade is expanded. */
     fun shouldHideStatusBarIconsWhenExpanded(): Boolean
 
-    /** Sets a listener to be notified when touch tracking begins. */
-    fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener)
-
     /**
      * Disables the shade header.
      *
@@ -250,17 +241,3 @@
     /** Return the fraction of the shade that's expanded, when in lockscreen. */
     val lockscreenShadeDragProgress: Float
 }
-
-/** Listens for when touch tracking begins. */
-interface TrackingStartedListener {
-    fun onTrackingStarted()
-}
-
-/** Listens for when shade begins opening or finishes closing. */
-interface OpenCloseListener {
-    /** Called when the shade finishes closing. */
-    fun onClosingFinished()
-
-    /** Called when the shade starts opening. */
-    fun onOpenStarted()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 6e03686..b67156f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -34,7 +34,6 @@
     ShadeLockscreenInteractor,
     PanelExpansionInteractor {
     override fun expandToNotifications() {}
-    override val isExpandingOrCollapsing: Boolean = false
     override val isExpanded: Boolean = false
     override val isPanelExpanded: Boolean = false
     override fun animateCollapseQs(fullyCollapse: Boolean) {}
@@ -43,10 +42,8 @@
     override val isFullyCollapsed: Boolean = false
     override val isTracking: Boolean = false
     override val isViewEnabled: Boolean = false
-    override fun setOpenCloseListener(openCloseListener: OpenCloseListener) {}
     override fun shouldHideStatusBarIconsWhenExpanded() = false
     override fun blockExpansionForCurrentTouch() {}
-    override fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener) {}
     override fun disableHeader(state1: Int, state2: Int, animated: Boolean) {}
     override fun startExpandLatencyTracking() {}
     override fun startBouncerPreHideAnimation() {}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt b/packages/SystemUI/src/com/android/systemui/shade/TrackingStartedListener.kt
similarity index 65%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
copy to packages/SystemUI/src/com/android/systemui/shade/TrackingStartedListener.kt
index 98a9e93..3803c27 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/TrackingStartedListener.kt
@@ -14,16 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.ui.screens
+package com.android.systemui.shade
 
-import androidx.activity.result.IntentSenderRequest
-
-sealed class UiState {
-    data object CredentialScreen : UiState()
-
-    data class CredentialSelected(
-        val intentSenderRequest: IntentSenderRequest?
-    ) : UiState()
-
-    data object Cancel : UiState()
+/** Listens for when touch tracking begins. */
+interface TrackingStartedListener {
+    fun onTrackingStarted()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index e050c0b..5c79e1e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.shade.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -100,8 +101,7 @@
     @Deprecated("Use ShadeInteractor.isQsBypassingShade instead")
     val legacyExpandImmediate: StateFlow<Boolean>
 
-    /** Whether the current configuration requires the split shade to be shown. */
-    val isSplitShade: StateFlow<Boolean>
+    val shadeMode: StateFlow<ShadeMode>
 
     /** True when QS is taking up the entire screen, i.e. fully expanded on a non-unfolded phone. */
     @Deprecated("Use ShadeInteractor instead") val legacyQsFullscreen: StateFlow<Boolean>
@@ -109,7 +109,7 @@
     /** NPVC.mClosing as a flow. */
     @Deprecated("Use ShadeAnimationInteractor instead") val legacyIsClosing: StateFlow<Boolean>
 
-    fun setSplitShade(isSplitShade: Boolean)
+    fun setShadeMode(mode: ShadeMode)
 
     /** Sets whether a closing animation is happening. */
     @Deprecated("Use ShadeAnimationInteractor instead") fun setLegacyIsClosing(isClosing: Boolean)
@@ -219,11 +219,11 @@
     @Deprecated("Use ShadeInteractor instead")
     override val legacyQsFullscreen: StateFlow<Boolean> = _legacyQsFullscreen.asStateFlow()
 
-    val _isSplitShade = MutableStateFlow(false)
-    override val isSplitShade: StateFlow<Boolean> = _isSplitShade.asStateFlow()
+    val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
+    override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
 
-    override fun setSplitShade(isSplitShade: Boolean) {
-        _isSplitShade.value = isSplitShade
+    override fun setShadeMode(shadeMode: ShadeMode) {
+        _shadeMode.value = shadeMode
     }
 
     override fun setLegacyQsFullscreen(legacyQsFullscreen: Boolean) {
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 ad3fbe5..bc60c83 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade.domain.interactor
 
+import com.android.systemui.shade.shared.model.ShadeMode
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
@@ -103,8 +104,7 @@
      */
     val isUserInteractingWithQs: Flow<Boolean>
 
-    /** Whether the current configuration requires the split shade to be shown. */
-    val isSplitShade: StateFlow<Boolean>
+    val shadeMode: StateFlow<ShadeMode>
 }
 
 fun createAnyExpansionFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 57a36b5..e9bb4c6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.shade.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -42,5 +43,5 @@
     override val isUserInteracting: StateFlow<Boolean> = inactiveFlowBoolean
     override val isShadeTouchable: Flow<Boolean> = inactiveFlowBoolean
     override val isExpandToQsEnabled: Flow<Boolean> = inactiveFlowBoolean
-    override val isSplitShade: StateFlow<Boolean> = inactiveFlowBoolean
+    override val shadeMode: StateFlow<ShadeMode> = MutableStateFlow(ShadeMode.Single)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index 3bccd2b..6414af3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -99,7 +100,7 @@
     override val isUserInteractingWithQs: Flow<Boolean> =
         userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion)
 
-    override val isSplitShade: StateFlow<Boolean> = repository.isSplitShade
+    override val shadeMode: StateFlow<ShadeMode> = repository.shadeMode
 
     /**
      * Return a flow for whether a user is interacting with an expandable shade component using
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index ad8c029..7785eda 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -108,7 +109,7 @@
     override val isUserInteractingWithQs: Flow<Boolean> =
         sceneBasedInteracting(sceneInteractor, Scenes.QuickSettings)
 
-    override val isSplitShade: StateFlow<Boolean> = shadeRepository.isSplitShade
+    override val shadeMode: StateFlow<ShadeMode> = shadeRepository.shadeMode
 
     /**
      * Returns a flow that uses scene transition progress to and from a scene that is pulled down
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index 1f78ae8..3d9337e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -38,8 +38,6 @@
         changeToShadeScene()
     }
 
-    override val isExpandingOrCollapsing = shadeInteractor.isUserInteracting.value
-
     override val isExpanded = shadeInteractor.isAnyExpanded.value
 
     override fun startBouncerPreHideAnimation() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index 334908e..11ce818 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -41,17 +42,25 @@
 ) : CoreStartable {
 
     override fun start() {
-        hydrateSplitShade()
+        hydrateShadeMode()
     }
 
-    private fun hydrateSplitShade() {
+    private fun hydrateShadeMode() {
         applicationScope.launch {
             configurationRepository.onAnyConfigurationChange
                 // Force initial collection.
                 .onStart { emit(Unit) }
                 .map { applicationContext.resources }
                 .map { resources -> controller.shouldUseSplitNotificationShade(resources) }
-                .collect { isSplitShade -> shadeRepository.setSplitShade(isSplitShade) }
+                .collect { isSplitShade ->
+                    shadeRepository.setShadeMode(
+                        if (isSplitShade) {
+                            ShadeMode.Split
+                        } else {
+                            ShadeMode.Single
+                        }
+                    )
+                }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
new file mode 100644
index 0000000..3451eaf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.shared.model
+
+/** Enumerates all known modes of operation of the shade. */
+sealed interface ShadeMode {
+
+    /**
+     * The single or "accordion" shade where the QS and notification parts are in two vertically
+     * stacked panels and the user can swipe up and down to expand or collapse between the two
+     * parts.
+     */
+    data object Single : ShadeMode
+
+    /**
+     * The split shade where, on large screens and unfolded foldables, the QS and notification parts
+     * are placed side-by-side and expand/collapse as a single panel.
+     */
+    data object Split : ShadeMode
+
+    /**
+     * The dual shade where the QS and notification parts each have their own independently
+     * expandable/collapsible panel on either side of the large screen / unfolded device or sharing
+     * a space on a small screen or folded device.
+     */
+    data object Dual : ShadeMode
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 8084a6f..ea549f2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
@@ -64,12 +65,12 @@
         combine(
                 deviceEntryInteractor.isUnlocked,
                 deviceEntryInteractor.canSwipeToEnter,
-                shadeInteractor.isSplitShade,
-            ) { isUnlocked, canSwipeToDismiss, isSplitShade ->
+                shadeInteractor.shadeMode,
+            ) { isUnlocked, canSwipeToDismiss, shadeMode ->
                 destinationScenes(
                     isUnlocked = isUnlocked,
                     canSwipeToDismiss = canSwipeToDismiss,
-                    isSplitShade = isSplitShade,
+                    shadeMode = shadeMode,
                 )
             }
             .stateIn(
@@ -79,7 +80,7 @@
                     destinationScenes(
                         isUnlocked = deviceEntryInteractor.isUnlocked.value,
                         canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value,
-                        isSplitShade = shadeInteractor.isSplitShade.value,
+                        shadeMode = shadeInteractor.shadeMode.value,
                     ),
             )
 
@@ -96,8 +97,7 @@
                 initialValue = false
             )
 
-    /** Whether the current configuration requires the split shade to be shown. */
-    val isSplitShade: StateFlow<Boolean> = shadeInteractor.isSplitShade
+    val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
 
     /** Notifies that some content in the shade was clicked. */
     fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry()
@@ -119,7 +119,7 @@
     private fun destinationScenes(
         isUnlocked: Boolean,
         canSwipeToDismiss: Boolean?,
-        isSplitShade: Boolean,
+        shadeMode: ShadeMode,
     ): Map<UserAction, UserActionResult> {
         val up =
             when {
@@ -128,7 +128,7 @@
                 else -> Scenes.Lockscreen
             }
 
-        val down = if (isSplitShade) null else Scenes.QuickSettings
+        val down = Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single }
 
         return buildMap {
             this[Swipe(SwipeDirection.Up)] = UserActionResult(up)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 6155348..5171a5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -39,8 +39,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.notification.ColorUpdateLogger;
@@ -95,8 +93,6 @@
     private float mCornerAnimationDistance;
     private float mActualWidth = -1;
     private int mMaxIconsOnLockscreen;
-    private final RefactorFlag mSensitiveRevealAnim =
-            RefactorFlag.forView(Flags.SENSITIVE_REVEAL_ANIM);
     private boolean mCanModifyColorOfNotifications;
     private boolean mCanInteract;
     private NotificationStackScrollLayout mHostLayout;
@@ -266,7 +262,7 @@
         }
 
         final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
-        if (mSensitiveRevealAnim.isEnabled() && viewState.hidden) {
+        if (viewState.hidden) {
             // if the shelf is hidden, position it at the end of the stack (plus the clip
             // padding), such that when it appears animated, it will smoothly move in from the
             // bottom, without jump cutting any notifications
@@ -398,10 +394,6 @@
         //  find the first view that doesn't overlap with the shelf
         int notGoneIndex = 0;
         int colorOfViewBeforeLast = NO_COLOR;
-        boolean backgroundForceHidden = false;
-        if (mHideBackground && !((ShelfState) getViewState()).hasItemsInStableShelf) {
-            backgroundForceHidden = true;
-        }
         int colorTwoBefore = NO_COLOR;
         int previousColor = NO_COLOR;
         float transitionAmount = 0.0f;
@@ -429,8 +421,7 @@
                     expandingAnimated, isLastChild, shelfClipStart);
 
             // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
-            if ((!mSensitiveRevealAnim.isEnabled() && ((isLastChild && !child.isInShelf())
-                    || backgroundForceHidden)) || aboveShelf) {
+            if (aboveShelf) {
                 notificationClipEnd = shelfStart + getIntrinsicHeight();
             } else {
                 notificationClipEnd = shelfStart - mPaddingBetweenElements;
@@ -440,8 +431,7 @@
 
             // If the current row is an ExpandableNotificationRow, update its color, roundedness,
             // and icon state.
-            if (child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow expandableRow = (ExpandableNotificationRow) child;
+            if (child instanceof ExpandableNotificationRow expandableRow) {
                 numViewsInShelf += inShelfAmount;
                 int ownColorUntinted = expandableRow.getBackgroundColorWithoutTint();
                 if (viewStart >= shelfStart && mNotGoneIndex == -1) {
@@ -471,16 +461,8 @@
                 notGoneIndex++;
             }
 
-            if (child instanceof ActivatableNotificationView) {
-                ActivatableNotificationView anv = (ActivatableNotificationView) child;
-                // Because we show whole notifications on the lockscreen, the bottom notification is
-                // always "just about to enter the shelf" by normal scrolling rules.  This is fine
-                // if the shelf is visible, but if the shelf is hidden, it causes incorrect curling.
-                // notificationClipEnd handles the discrepancy between a visible and hidden shelf,
-                // so we use that when on the keyguard (and while animating away) to reduce curling.
-                final float keyguardSafeShelfStart = !mSensitiveRevealAnim.isEnabled()
-                        && mAmbientState.isOnKeyguard() ? notificationClipEnd : shelfStart;
-                updateCornerRoundnessOnScroll(anv, viewStart, keyguardSafeShelfStart);
+            if (child instanceof ActivatableNotificationView anv) {
+                updateCornerRoundnessOnScroll(anv, viewStart, shelfStart);
             }
         }
 
@@ -519,11 +501,10 @@
         mShelfIcons.applyIconStates();
         for (int i = 0; i < getHostLayoutChildCount(); i++) {
             View child = getHostLayoutChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)
+            if (!(child instanceof ExpandableNotificationRow row)
                     || child.getVisibility() == GONE) {
                 continue;
             }
-            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
             updateContinuousClipping(row);
         }
         boolean hideBackground = isHidden;
@@ -613,8 +594,7 @@
     private void clipTransientViews() {
         for (int i = 0; i < getHostLayoutTransientViewCount(); i++) {
             View transientView = getHostLayoutTransientView(i);
-            if (transientView instanceof ExpandableView) {
-                ExpandableView transientExpandableView = (ExpandableView) transientView;
+            if (transientView instanceof ExpandableView transientExpandableView) {
                 updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1);
             }
         }
@@ -871,10 +851,9 @@
     }
 
     private void setIconTransformationAmount(ExpandableView view, float transitionAmount) {
-        if (!(view instanceof ExpandableNotificationRow)) {
+        if (!(view instanceof ExpandableNotificationRow row)) {
             return;
         }
-        ExpandableNotificationRow row = (ExpandableNotificationRow) view;
         StatusBarIconView icon = row.getShelfIcon();
         NotificationIconContainer.IconState iconState = getIconState(icon);
         if (iconState == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5f3a83a..589537e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -278,8 +278,6 @@
     private OnExpandClickListener mOnExpandClickListener;
     private View.OnClickListener mOnFeedbackClickListener;
     private Path mExpandingClipPath;
-    private final RefactorFlag mInlineReplyAnimation =
-            RefactorFlag.forView(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
 
     private static boolean shouldSimulateSlowMeasure() {
         return Compile.IS_DEBUG && RefactorFlag.forView(
@@ -2880,8 +2878,7 @@
         mSensitiveHiddenInGeneral = hideSensitive;
         int intrinsicAfter = getIntrinsicHeight();
         if (intrinsicBefore != intrinsicAfter) {
-            boolean needsAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
-            notifyHeightChanged(needsAnimation);
+            notifyHeightChanged(true);
         }
     }
 
@@ -3241,13 +3238,8 @@
             mGuts.setActualHeight(height);
             return;
         }
-        int contentHeight = Math.max(getMinHeight(), height);
         for (NotificationContentView l : mLayouts) {
-            if (mInlineReplyAnimation.isEnabled()) {
-                l.setContentHeight(height);
-            } else {
-                l.setContentHeight(contentHeight);
-            }
+            l.setContentHeight(height);
         }
         if (mIsSummaryWithChildren) {
             mChildrenContainer.setActualHeight(height);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 137e1b2..8a3e7e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -699,8 +699,7 @@
         int hint;
         if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
             hint = getViewHeight(VISIBLE_TYPE_HEADSUP);
-            if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance()
-                    && mHeadsUpRemoteInputController.isFocusAnimationFlagActive()) {
+            if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance()) {
                 // While the RemoteInputView is animating its appearance, it should be allowed
                 // to overlap the hint, therefore no space is reserved for the hint during the
                 // appearance animation of the RemoteInputView
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index b205071..77e9425 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -87,7 +87,6 @@
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.res.R;
@@ -197,8 +196,6 @@
      */
     private Set<Integer> mDebugTextUsedYPositions;
     private final boolean mDebugRemoveAnimation;
-    private final boolean mSensitiveRevealAnimEndabled;
-    private final RefactorFlag mAnimatedInsets;
     private int mContentHeight;
     private float mIntrinsicContentHeight;
     private int mPaddingBetweenElements;
@@ -619,9 +616,6 @@
                 Flags.LOCKSCREEN_ENABLE_LANDSCAPE);
         mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
         mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
-        mSensitiveRevealAnimEndabled = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
-        mAnimatedInsets =
-                new RefactorFlag(mFeatureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
         mSectionsManager = Dependency.get(NotificationSectionsManager.class);
         mScreenOffAnimationController =
                 Dependency.get(ScreenOffAnimationController.class);
@@ -656,9 +650,7 @@
         mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
         mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-        if (mAnimatedInsets.isEnabled()) {
-            setWindowInsetsAnimationCallback(mInsetsCallback);
-        }
+        setWindowInsetsAnimationCallback(mInsetsCallback);
     }
 
     /**
@@ -1734,11 +1726,7 @@
             return;
         }
         mForcedScroll = v;
-        if (mAnimatedInsets.isEnabled()) {
-            updateForcedScroll();
-        } else {
-            scrollTo(v);
-        }
+        updateForcedScroll();
     }
 
     public boolean scrollTo(View v) {
@@ -1783,31 +1771,15 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        if (!mAnimatedInsets.isEnabled()) {
-            mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
-        }
         mWaterfallTopInset = 0;
         final DisplayCutout cutout = insets.getDisplayCutout();
         if (cutout != null) {
             mWaterfallTopInset = cutout.getWaterfallInsets().top;
         }
-        if (mAnimatedInsets.isEnabled() && !mIsInsetAnimationRunning) {
+        if (!mIsInsetAnimationRunning) {
             // update bottom inset e.g. after rotation
             updateBottomInset(insets);
         }
-        if (!mAnimatedInsets.isEnabled()) {
-            int range = getScrollRange();
-            if (mOwnScrollY > range) {
-                // HACK: We're repeatedly getting staggered insets here while the IME is
-                // animating away. To work around that we'll wait until things have settled.
-                removeCallbacks(mReclamp);
-                postDelayed(mReclamp, 50);
-            } else if (mForcedScroll != null) {
-                // The scroll was requested before we got the actual inset - in case we need
-                // to scroll up some more do so now.
-                scrollTo(mForcedScroll);
-            }
-        }
         return insets;
     }
 
@@ -2576,7 +2548,7 @@
             return;
         }
         child.setOnHeightChangedListener(null);
-        if (child instanceof ExpandableNotificationRow && mSensitiveRevealAnimEndabled) {
+        if (child instanceof ExpandableNotificationRow) {
             NotificationEntry entry = ((ExpandableNotificationRow) child).getEntry();
             entry.removeOnSensitivityChangedListener(mOnChildSensitivityChangedListener);
         }
@@ -2872,7 +2844,7 @@
     private void onViewAddedInternal(ExpandableView child) {
         updateHideSensitiveForChild(child);
         child.setOnHeightChangedListener(mOnChildHeightChangedListener);
-        if (child instanceof ExpandableNotificationRow && mSensitiveRevealAnimEndabled) {
+        if (child instanceof ExpandableNotificationRow) {
             NotificationEntry entry = ((ExpandableNotificationRow) child).getEntry();
             entry.addOnSensitivityChangedListener(mOnChildSensitivityChangedListener);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 84b39c1..2745817 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -20,7 +20,6 @@
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
 import com.android.systemui.common.shared.model.NotificationContainerBounds
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dump.DumpManager
@@ -37,8 +36,6 @@
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
 import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
-import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
-import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
@@ -97,7 +94,6 @@
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val shadeInteractor: ShadeInteractor,
-    communalInteractor: CommunalInteractor,
     private val alternateBouncerToGoneTransitionViewModel:
         AlternateBouncerToGoneTransitionViewModel,
     private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
@@ -127,22 +123,6 @@
     private val statesForConstrainedNotifications: Set<KeyguardState> =
         setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
 
-    private val lockscreenToGlanceableHubRunning =
-        keyguardTransitionInteractor
-            .transition(LOCKSCREEN, GLANCEABLE_HUB)
-            .map { it.transitionState == STARTED || it.transitionState == RUNNING }
-            .distinctUntilChanged()
-            .onStart { emit(false) }
-            .dumpWhileCollecting("lockscreenToGlanceableHubRunning")
-
-    private val glanceableHubToLockscreenRunning =
-        keyguardTransitionInteractor
-            .transition(GLANCEABLE_HUB, LOCKSCREEN)
-            .map { it.transitionState == STARTED || it.transitionState == RUNNING }
-            .distinctUntilChanged()
-            .onStart { emit(false) }
-            .dumpWhileCollecting("glanceableHubToLockscreenRunning")
-
     /**
      * Shade locked is a legacy concept, but necessary to mimic current functionality. Listen for
      * both SHADE_LOCKED and shade/qs expansion in order to determine lock state, as one can arrive
@@ -218,21 +198,38 @@
             )
             .dumpValue("isOnLockscreenWithoutShade")
 
+    /** If the user is visually on the glanceable hub or transitioning to/from it */
+    private val isOnGlanceableHub: Flow<Boolean> =
+        combine(
+                keyguardTransitionInteractor.finishedKeyguardState.map { state ->
+                    state == GLANCEABLE_HUB
+                },
+                keyguardTransitionInteractor
+                    .isInTransitionWhere { from, to ->
+                        from == GLANCEABLE_HUB || to == GLANCEABLE_HUB
+                    }
+                    .onStart { emit(false) }
+            ) { isOnGlanceableHub, transitioningToOrFromHub ->
+                isOnGlanceableHub || transitioningToOrFromHub
+            }
+            .distinctUntilChanged()
+            .dumpWhileCollecting("isOnGlanceableHub")
+
     /** Are we purely on the glanceable hub without the shade/qs? */
     val isOnGlanceableHubWithoutShade: Flow<Boolean> =
         combine(
-                communalInteractor.isIdleOnCommunal,
+                isOnGlanceableHub,
                 // Shade with notifications
                 shadeInteractor.shadeExpansion.map { it > 0f },
                 // Shade without notifications, quick settings only (pull down from very top on
                 // lockscreen)
                 shadeInteractor.qsExpansion.map { it > 0f },
-            ) { isIdleOnCommunal, isShadeVisible, qsExpansion ->
-                isIdleOnCommunal && !(isShadeVisible || qsExpansion)
+            ) { isGlanceableHub, isShadeVisible, qsExpansion ->
+                isGlanceableHub && !(isShadeVisible || qsExpansion)
             }
             .stateIn(
                 scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
+                started = SharingStarted.Eagerly,
                 initialValue = false,
             )
             .dumpWhileCollecting("isOnGlanceableHubWithoutShade")
@@ -431,39 +428,35 @@
     }
 
     /**
-     * Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB transition
-     * or idle on the glanceable hub.
+     * Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB or
+     * DREAMING<->GLANCEABLE_HUB transition or idle on the hub.
      *
      * Must return 1.0f when not controlling the alpha since notifications does a min of all the
      * alpha sources.
      */
     val glanceableHubAlpha: Flow<Float> =
-        isOnGlanceableHubWithoutShade
-            .flatMapLatest { isOnGlanceableHubWithoutShade ->
-                combineTransform(
-                    lockscreenToGlanceableHubRunning,
-                    glanceableHubToLockscreenRunning,
-                    merge(
-                        lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
-                        glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
-                    )
-                ) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha ->
-                    if (isOnGlanceableHubWithoutShade) {
-                        // Notifications should not be visible on the glanceable hub.
-                        // TODO(b/321075734): implement a way to actually set the notifications to
-                        // gone
-                        //  while on the hub instead of just adjusting alpha
-                        emit(0f)
-                    } else if (
-                        lockscreenToGlanceableHubRunning || glanceableHubToLockscreenRunning
-                    ) {
-                        emit(alpha)
-                    } else {
-                        // Not on the hub and no transitions running, return full visibility so we
-                        // don't
-                        // block the notifications from showing.
-                        emit(1f)
-                    }
+        combineTransform(
+                isOnGlanceableHubWithoutShade,
+                isOnLockscreen,
+                merge(
+                    lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
+                    glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
+                )
+            ) { isOnGlanceableHubWithoutShade, isOnLockscreen, alpha,
+                ->
+                if (isOnGlanceableHubWithoutShade && !isOnLockscreen) {
+                    // Notifications should not be visible on the glanceable hub.
+                    // TODO(b/321075734): implement a way to actually set the notifications to
+                    // gone while on the hub instead of just adjusting alpha
+                    emit(0f)
+                } else if (isOnGlanceableHubWithoutShade) {
+                    // We are transitioning between hub and lockscreen, so set the alpha for the
+                    // transition animation.
+                    emit(alpha)
+                } else {
+                    // Not on the hub and no transitions running, return full visibility so we
+                    // don't block the notifications from showing.
+                    emit(1f)
                 }
             }
             .dumpWhileCollecting("glanceableHubAlpha")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 23a080b..a55de25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -648,6 +648,10 @@
                 }
                 if (intent.isActivity) {
                     assistManagerLazy.get().hideAssist()
+                    // This activity could have started while the device is dreaming, in which case
+                    // the dream would occlude the activity. In order to show the newly started
+                    // activity, we wake from the dream.
+                    keyguardUpdateMonitor.awakenFromDream()
                 }
                 intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
             }
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 e2e13a1..ab9ecab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -313,7 +313,7 @@
                 mHeadsUpManager.unpinAll(true /* userUnpinned */);
                 mMetricsLogger.count("panel_open", 1);
             } else if (!mQsController.getExpanded()
-                    && !mShadeViewController.isExpandingOrCollapsing()) {
+                    && !mShadeController.isExpandingOrCollapsing()) {
                 mShadeController.animateExpandQs();
                 mMetricsLogger.count("panel_open_qs", 1);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index ba89d4a..7dd328a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -579,7 +579,7 @@
         final boolean hideBouncerOverDream =
                 mDreamOverlayStateController.isOverlayActive()
                         && (mShadeLockscreenInteractor.isExpanded()
-                        || mShadeLockscreenInteractor.isExpandingOrCollapsing());
+                        || mShadeController.get().isExpandingOrCollapsing());
 
         final boolean isUserTrackingStarted =
                 event.getFraction() != EXPANSION_HIDDEN && event.getTracking();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 5bced93..9633cb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -135,7 +135,6 @@
     @Nullable
     private RevealParams mRevealParams;
     private Rect mContentBackgroundBounds;
-    private boolean mIsFocusAnimationFlagActive;
     private boolean mIsAnimatingAppearance = false;
 
     // TODO(b/193539698): move these to a Controller
@@ -432,7 +431,7 @@
         // case to prevent flicker.
         if (!mRemoved) {
             ViewGroup parent = (ViewGroup) getParent();
-            if (animate && parent != null && mIsFocusAnimationFlagActive) {
+            if (animate && parent != null) {
 
                 ViewGroup grandParent = (ViewGroup) parent.getParent();
                 View actionsContainer = getActionsContainerLayout();
@@ -497,8 +496,7 @@
     }
 
     private void setTopMargin(int topMargin) {
-        if (!(getLayoutParams() instanceof FrameLayout.LayoutParams)) return;
-        final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
+        if (!(getLayoutParams() instanceof FrameLayout.LayoutParams layoutParams)) return;
         layoutParams.topMargin = topMargin;
         setLayoutParams(layoutParams);
     }
@@ -608,24 +606,10 @@
     }
 
     /**
-     * Sets whether the feature flag for the revised inline reply animation is active or not.
-     * @param active
-     */
-    public void setIsFocusAnimationFlagActive(boolean active) {
-        mIsFocusAnimationFlagActive = active;
-    }
-
-    /**
      * Focuses the RemoteInputView and animates its appearance
      */
     public void focusAnimated() {
-        if (!mIsFocusAnimationFlagActive && getVisibility() != VISIBLE
-                && mRevealParams != null) {
-            android.animation.Animator animator = mRevealParams.createCircularRevealAnimator(this);
-            animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-            animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-            animator.start();
-        } else if (mIsFocusAnimationFlagActive && getVisibility() != VISIBLE) {
+        if (getVisibility() != VISIBLE) {
             mIsAnimatingAppearance = true;
             setAlpha(0f);
             Animator focusAnimator = getFocusAnimator(getActionsContainerLayout());
@@ -680,37 +664,19 @@
     }
 
     private void reset() {
-        if (mIsFocusAnimationFlagActive) {
-            mProgressBar.setVisibility(INVISIBLE);
-            mResetting = true;
-            mSending = false;
-            mController.removeSpinning(mEntry.getKey(), mToken);
-            onDefocus(true /* animate */, false /* logClose */, () -> {
-                mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
-                mEditText.getText().clear();
-                mEditText.setEnabled(isAggregatedVisible());
-                mSendButton.setVisibility(VISIBLE);
-                updateSendButton();
-                setAttachment(null);
-                mResetting = false;
-            });
-            return;
-        }
-
+        mProgressBar.setVisibility(INVISIBLE);
         mResetting = true;
         mSending = false;
-        mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
-
-        mEditText.getText().clear();
-        mEditText.setEnabled(isAggregatedVisible());
-        mSendButton.setVisibility(VISIBLE);
-        mProgressBar.setVisibility(INVISIBLE);
         mController.removeSpinning(mEntry.getKey(), mToken);
-        updateSendButton();
-        onDefocus(false /* animate */, false /* logClose */, null /* doAfterDefocus */);
-        setAttachment(null);
-
-        mResetting = false;
+        onDefocus(true /* animate */, false /* logClose */, () -> {
+            mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
+            mEditText.getText().clear();
+            mEditText.setEnabled(isAggregatedVisible());
+            mSendButton.setVisibility(VISIBLE);
+            updateSendButton();
+            setAttachment(null);
+            mResetting = false;
+        });
     }
 
     @Override
@@ -854,7 +820,7 @@
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
-        if (mIsFocusAnimationFlagActive) setPivotY(getMeasuredHeight());
+        setPivotY(getMeasuredHeight());
         if (mContentBackgroundBounds != null) {
             mContentBackground.setBounds(mContentBackgroundBounds);
         }
@@ -1015,9 +981,9 @@
 
         private RemoteInputView mRemoteInputView;
         boolean mShowImeOnInputConnection;
-        private LightBarController mLightBarController;
+        private final LightBarController mLightBarController;
         private InputMethodManager mInputMethodManager;
-        private ArraySet<String> mSupportedMimes = new ArraySet<>();
+        private final ArraySet<String> mSupportedMimes = new ArraySet<>();
         UserHandle mUser;
 
         public RemoteEditText(Context context, AttributeSet attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index 6c0d433..bfee9ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -32,7 +32,6 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.res.R
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION
 import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.RemoteInputController
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -64,8 +63,6 @@
 
     var revealParams: RevealParams?
 
-    val isFocusAnimationFlagActive: Boolean
-
     /**
      * Sets the smart reply that should be inserted in the remote input, or `null` if the user is
      * not editing a smart reply.
@@ -155,9 +152,6 @@
 
     override val isActive: Boolean get() = view.isActive
 
-    override val isFocusAnimationFlagActive: Boolean
-        get() = mFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION)
-
     override fun bind() {
         if (isBound) return
         isBound = true
@@ -168,7 +162,6 @@
             view.setSupportedMimeTypes(it.allowedDataTypes)
         }
         view.setRevealParameters(revealParams)
-        view.setIsFocusAnimationFlagActive(isFocusAnimationFlagActive)
 
         view.addOnEditTextFocusChangedListener(onFocusChangeListener)
         view.addOnSendRemoteInputListener(onSendRemoteInputListener)
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
index 5c53ff9..ac1d280 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.unfold
 
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
 import android.animation.ValueAnimator
 import android.annotation.BinderThread
 import android.content.Context
@@ -23,7 +25,6 @@
 import android.os.SystemProperties
 import android.util.Log
 import android.view.animation.DecelerateInterpolator
-import androidx.core.animation.addListener
 import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.display.data.repository.DeviceStateRepository
@@ -36,17 +37,25 @@
 import com.android.systemui.unfold.dagger.UnfoldBg
 import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
 import javax.inject.Inject
+import kotlin.coroutines.resume
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.TimeoutCancellationException
 import kotlinx.coroutines.android.asCoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onCompletion
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withTimeout
 
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
 class FoldLightRevealOverlayAnimation
 @Inject
 constructor(
@@ -61,6 +70,9 @@
 
     private val revealProgressValueAnimator: ValueAnimator =
         ValueAnimator.ofFloat(ALPHA_OPAQUE, ALPHA_TRANSPARENT)
+    private val areAnimationEnabled: Flow<Boolean>
+        get() = animationStatusRepository.areAnimationsEnabled()
+
     private lateinit var controller: FullscreenLightRevealAnimationController
     @Volatile private var readyCallback: CompletableDeferred<Runnable>? = null
 
@@ -89,33 +101,31 @@
 
         applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
             deviceStateRepository.state
-                .map { it != DeviceStateRepository.DeviceState.FOLDED }
+                .map { it == DeviceStateRepository.DeviceState.FOLDED }
                 .distinctUntilChanged()
-                .filter { isUnfolded -> isUnfolded }
-                .collect { controller.ensureOverlayRemoved() }
-        }
-
-        applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
-            deviceStateRepository.state
-                .filter {
-                    animationStatusRepository.areAnimationsEnabled().first() &&
-                        it == DeviceStateRepository.DeviceState.FOLDED
-                }
-                .collect {
-                    try {
-                        withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
-                            readyCallback = CompletableDeferred()
-                            val onReady = readyCallback?.await()
-                            readyCallback = null
-                            controller.addOverlay(ALPHA_OPAQUE, onReady)
-                            waitForScreenTurnedOn()
+                .flatMapLatest { isFolded ->
+                    flow<Nothing> {
+                            if (!areAnimationEnabled.first() || !isFolded) {
+                                return@flow
+                            }
+                            withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
+                                readyCallback = CompletableDeferred()
+                                val onReady = readyCallback?.await()
+                                readyCallback = null
+                                controller.addOverlay(ALPHA_OPAQUE, onReady)
+                                waitForScreenTurnedOn()
+                            }
                             playFoldLightRevealOverlayAnimation()
                         }
-                    } catch (e: TimeoutCancellationException) {
-                        Log.e(TAG, "Fold light reveal animation timed out")
-                        ensureOverlayRemovedInternal()
-                    }
+                        .catchTimeoutAndLog()
+                        .onCompletion {
+                            controller.ensureOverlayRemoved()
+                            val onReady = readyCallback?.takeIf { it.isCompleted }?.getCompleted()
+                            onReady?.run()
+                            readyCallback = null
+                        }
                 }
+                .collect {}
         }
     }
 
@@ -128,19 +138,34 @@
         powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
     }
 
-    private fun ensureOverlayRemovedInternal() {
-        revealProgressValueAnimator.cancel()
-        controller.ensureOverlayRemoved()
-    }
-
-    private fun playFoldLightRevealOverlayAnimation() {
+    private suspend fun playFoldLightRevealOverlayAnimation() {
         revealProgressValueAnimator.duration = ANIMATION_DURATION
         revealProgressValueAnimator.interpolator = DecelerateInterpolator()
         revealProgressValueAnimator.addUpdateListener { animation ->
             controller.updateRevealAmount(animation.animatedFraction)
         }
-        revealProgressValueAnimator.addListener(onEnd = { controller.ensureOverlayRemoved() })
-        revealProgressValueAnimator.start()
+        revealProgressValueAnimator.startAndAwaitCompletion()
+    }
+
+    private suspend fun ValueAnimator.startAndAwaitCompletion(): Unit =
+        suspendCancellableCoroutine { continuation ->
+            val listener =
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator) {
+                        continuation.resume(Unit)
+                        removeListener(this)
+                    }
+                }
+            addListener(listener)
+            continuation.invokeOnCancellation { removeListener(listener) }
+            start()
+        }
+
+    private fun <T> Flow<T>.catchTimeoutAndLog() = catch { exception ->
+        when (exception) {
+            is TimeoutCancellationException -> Log.e(TAG, "Fold light reveal animation timed out")
+            else -> throw exception
+        }
     }
 
     private companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index dd428f5..ccdcee5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -32,7 +32,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -57,7 +56,6 @@
 
     @Before
     public void setUp() throws Exception {
-        mFeatureFlags.setDefault(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectMockDependency(NotificationMediaManager.class);
         allowTestableLooperAsMainThread();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 0bd4cbe..1849245 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -93,11 +93,11 @@
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.SystemPropertiesHelper;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -220,7 +220,7 @@
     private boolean mKeyguardGoingAway = false;
 
     private @Mock CoroutineDispatcher mDispatcher;
-    private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
+    private @Mock DreamViewModel mDreamViewModel;
     private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
     private @Mock SceneContainerFlags mSceneContainerFlags;
 
@@ -241,9 +241,9 @@
         final ViewRootImpl testViewRoot = mock(ViewRootImpl.class);
         when(testViewRoot.getView()).thenReturn(mock(View.class));
         when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot);
-        when(mDreamingToLockscreenTransitionViewModel.getDreamOverlayAlpha())
+        when(mDreamViewModel.getDreamAlpha())
                 .thenReturn(mock(Flow.class));
-        when(mDreamingToLockscreenTransitionViewModel.getTransitionEnded())
+        when(mDreamViewModel.getTransitionEnded())
                 .thenReturn(mock(Flow.class));
         when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId);
         when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId);
@@ -1259,7 +1259,7 @@
                 mSystemSettings,
                 mSystemClock,
                 mDispatcher,
-                () -> mDreamingToLockscreenTransitionViewModel,
+                () -> mDreamViewModel,
                 mSystemPropertiesHelper,
                 () -> mock(WindowManagerLockscreenVisibilityManager.class),
                 mSelectedUserInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 22a2e93..f252163 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -130,6 +130,24 @@
             assertThat(value()).isEqualTo(LARGE)
         }
 
+    @Test
+    fun isLargeClockVisible_whenLargeClockSize_isTrue() =
+        scope.runTest {
+            fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1)
+            keyguardClockRepository.setClockSize(LARGE)
+            var value = collectLastValue(underTest.isLargeClockVisible)
+            assertThat(value()).isEqualTo(true)
+        }
+
+    @Test
+    fun isLargeClockVisible_whenSmallClockSize_isFalse() =
+        scope.runTest {
+            fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1)
+            keyguardClockRepository.setClockSize(SMALL)
+            var value = collectLastValue(underTest.isLargeClockVisible)
+            assertThat(value()).isEqualTo(false)
+        }
+
     private fun setupMockClock() {
         whenever(clock.largeClock).thenReturn(largeClock)
         whenever(largeClock.config).thenReturn(clockFaceConfig)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index bcec6109..b80dcd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -62,6 +62,7 @@
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth
 import kotlin.math.min
+import kotlin.test.assertEquals
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.emptyFlow
@@ -77,7 +78,6 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
-import kotlin.test.assertEquals
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -134,7 +134,12 @@
     private lateinit var lockscreenToPrimaryBouncerTransitionViewModel:
         LockscreenToPrimaryBouncerTransitionViewModel
     @Mock
-    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+    private lateinit var lockscreenToGlanceableHubTransitionViewModel:
+        LockscreenToGlanceableHubTransitionViewModel
+    @Mock
+    private lateinit var glanceableHubToLockscreenTransitionViewModel:
+        GlanceableHubToLockscreenTransitionViewModel
+    @Mock private lateinit var transitionInteractor: KeyguardTransitionInteractor
 
     private lateinit var underTest: KeyguardQuickAffordancesCombinedViewModel
 
@@ -271,6 +276,10 @@
         whenever(lockscreenToOccludedTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
         whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha)
             .thenReturn(emptyFlow())
+        whenever(lockscreenToGlanceableHubTransitionViewModel.shortcutsAlpha)
+            .thenReturn(emptyFlow())
+        whenever(glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha)
+            .thenReturn(emptyFlow())
         whenever(shadeInteractor.anyExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
         whenever(transitionInteractor.finishedKeyguardState)
             .thenReturn(intendedFinishedKeyguardStateFlow)
@@ -307,6 +316,8 @@
                 offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel,
                 primaryBouncerToLockscreenTransitionViewModel =
                     primaryBouncerToLockscreenTransitionViewModel,
+                glanceableHubToLockscreenTransitionViewModel =
+                    glanceableHubToLockscreenTransitionViewModel,
                 lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel,
                 lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel,
                 lockscreenToDreamingHostedTransitionViewModel =
@@ -316,6 +327,8 @@
                 lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
                 lockscreenToPrimaryBouncerTransitionViewModel =
                     lockscreenToPrimaryBouncerTransitionViewModel,
+                lockscreenToGlanceableHubTransitionViewModel =
+                    lockscreenToGlanceableHubTransitionViewModel,
                 transitionInteractor = transitionInteractor,
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index 3dc9037..0baee5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -274,8 +274,8 @@
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
             screenshotExecutor.onCloseSystemDialogsReceived()
-            verify(controller0).dismissScreenshot(any())
-            verify(controller1).dismissScreenshot(any())
+            verify(controller0).requestDismissal(any())
+            verify(controller1).requestDismissal(any())
 
             screenshotExecutor.onDestroy()
         }
@@ -290,8 +290,8 @@
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
             screenshotExecutor.onCloseSystemDialogsReceived()
-            verify(controller0, never()).dismissScreenshot(any())
-            verify(controller1).dismissScreenshot(any())
+            verify(controller0, never()).requestDismissal(any())
+            verify(controller1).requestDismissal(any())
 
             screenshotExecutor.onDestroy()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 62d2d0e..07d9350 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
@@ -76,6 +77,7 @@
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock private lateinit var shadeInteractor: ShadeInteractor
     @Mock private lateinit var powerManager: PowerManager
+    @Mock private lateinit var dialogFactory: SystemUIDialogFactory
 
     private lateinit var parentView: FrameLayout
     private lateinit var containerView: View
@@ -99,6 +101,7 @@
             GlanceableHubContainerController(
                 communalInteractor,
                 communalViewModel,
+                dialogFactory,
                 keyguardTransitionInteractor,
                 shadeInteractor,
                 powerManager
@@ -138,6 +141,7 @@
             GlanceableHubContainerController(
                 communalInteractor,
                 communalViewModel,
+                dialogFactory,
                 keyguardTransitionInteractor,
                 shadeInteractor,
                 powerManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 42a6924..b114e13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -60,7 +60,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
@@ -104,7 +103,6 @@
                 TestableLooper.get(this),
                 mFeatureFlags);
         mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
-        mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM);
     }
 
     @Test
@@ -186,14 +184,6 @@
     }
 
     @Test
-    public void testSetSensitiveOnNotifRowNotifiesOfHeightChange_withOtherFlagValue()
-            throws Exception {
-        FakeFeatureFlags flags = mFeatureFlags;
-        flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
-        testSetSensitiveOnNotifRowNotifiesOfHeightChange();
-    }
-
-    @Test
     public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception {
         // GIVEN a sensitive notification row that's currently redacted
         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
@@ -210,19 +200,10 @@
         // WHEN the row is set to no longer be sensitive
         row.setSensitive(false, true);
 
-        boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
         // VERIFY that the height change listener is invoked
         assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout());
         assertThat(row.getIntrinsicHeight()).isGreaterThan(0);
-        verify(listener).onHeightChanged(eq(row), eq(expectAnimation));
-    }
-
-    @Test
-    public void testSetSensitiveOnGroupRowNotifiesOfHeightChange_withOtherFlagValue()
-            throws Exception {
-        FakeFeatureFlags flags = mFeatureFlags;
-        flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
-        testSetSensitiveOnGroupRowNotifiesOfHeightChange();
+        verify(listener).onHeightChanged(eq(row), eq(true));
     }
 
     @Test
@@ -242,19 +223,10 @@
         // WHEN the row is set to no longer be sensitive
         group.setSensitive(false, true);
 
-        boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
         // VERIFY that the height change listener is invoked
         assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout());
         assertThat(group.getIntrinsicHeight()).isGreaterThan(0);
-        verify(listener).onHeightChanged(eq(group), eq(expectAnimation));
-    }
-
-    @Test
-    public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange_withOtherFlagValue()
-            throws Exception {
-        FakeFeatureFlags flags = mFeatureFlags;
-        flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
-        testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange();
+        verify(listener).onHeightChanged(eq(group), eq(true));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index b938029..9a7b8ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -10,7 +10,6 @@
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.res.R
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.NotificationShelf
@@ -23,7 +22,6 @@
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
-import org.junit.Assume.assumeTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,7 +36,6 @@
 @RunWithLooper
 open class NotificationShelfTest : SysuiTestCase() {
 
-    open val useSensitiveReveal: Boolean = false
     private val flags = FakeFeatureFlags()
 
     @Mock private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator
@@ -53,7 +50,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         mDependency.injectTestDependency(FeatureFlags::class.java, flags)
-        flags.set(Flags.SENSITIVE_REVEAL_ANIM, useSensitiveReveal)
         val root = FrameLayout(context)
         shelf =
             LayoutInflater.from(root.context)
@@ -335,7 +331,6 @@
     @Test
     fun updateState_withNullLastVisibleBackgroundChild_hideShelf() {
         // GIVEN
-        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -362,7 +357,6 @@
     @Test
     fun updateState_withNullFirstViewInShelf_hideShelf() {
         // GIVEN
-        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -389,7 +383,6 @@
     @Test
     fun updateState_withCollapsedShade_hideShelf() {
         // GIVEN
-        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -416,7 +409,6 @@
     @Test
     fun updateState_withHiddenSectionBeforeShelf_hideShelf() {
         // GIVEN
-        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -476,10 +468,3 @@
         assertEquals(expectedAlpha, shelf.viewState.alpha)
     }
 }
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper
-class NotificationShelfWithSensitiveRevealTest : NotificationShelfTest() {
-    override val useSensitiveReveal: Boolean = true
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 220305c..13df091 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -165,8 +165,6 @@
         // TODO: Ideally we wouldn't need to set these unless a test actually reads them,
         //  and then we would test both configurations, but currently they are all read
         //  in the constructor.
-        mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM);
-        mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
         mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION);
         mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 56fc7b9..1748cff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -22,6 +22,7 @@
 
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -38,6 +39,8 @@
 
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -226,7 +229,22 @@
     }
 
     @Test
-    public void onViewAttached_callbacksRegistered() {
+    @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    public void onViewAttached_updateUserSwitcherFlagEnabled_callbacksRegistered() {
+        mController.onViewAttached();
+
+        runAllScheduled();
+        verify(mConfigurationController).addCallback(any());
+        verify(mAnimationScheduler).addCallback(any());
+        verify(mUserInfoController).addCallback(any());
+        verify(mCommandQueue).addCallback(any());
+        verify(mStatusBarIconController).addIconGroup(any());
+        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+    }
+
+    @Test
+    @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    public void onViewAttached_updateUserSwitcherFlagDisabled_callbacksRegistered() {
         mController.onViewAttached();
 
         verify(mConfigurationController).addCallback(any());
@@ -238,7 +256,26 @@
     }
 
     @Test
-    public void onConfigurationChanged_updatesUserSwitcherVisibility() {
+    @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    public void
+            onConfigurationChanged_updateUserSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+        mController.onViewAttached();
+        runAllScheduled();
+        verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
+        clearInvocations(mUserManager);
+        clearInvocations(mKeyguardStatusBarView);
+
+        mConfigurationListenerCaptor.getValue().onConfigChanged(null);
+
+        runAllScheduled();
+        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+        verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
+    }
+
+    @Test
+    @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    public void
+            onConfigurationChanged_updateUserSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
         mController.onViewAttached();
         verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
         clearInvocations(mUserManager);
@@ -250,7 +287,26 @@
     }
 
     @Test
-    public void onKeyguardVisibilityChanged_updatesUserSwitcherVisibility() {
+    @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    public void
+            onKeyguardVisibilityChanged_userSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+        mController.onViewAttached();
+        runAllScheduled();
+        verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
+        clearInvocations(mUserManager);
+        clearInvocations(mKeyguardStatusBarView);
+
+        mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true);
+
+        runAllScheduled();
+        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+        verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
+    }
+
+    @Test
+    @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    public void
+            onKeyguardVisibilityChanged_userSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
         mController.onViewAttached();
         verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
         clearInvocations(mUserManager);
@@ -298,7 +354,7 @@
 
         verify(mStatusBarIconController).addIconGroup(any());
     }
-    
+
     @Test
     public void setBatteryListening_true_callbackAdded() {
         mController.setBatteryListening(true);
@@ -762,6 +818,11 @@
         return captor.getValue();
     }
 
+    private void runAllScheduled() {
+        mBackgroundExecutor.runAllReady();
+        mFakeExecutor.runAllReady();
+    }
+
     private static class TestShadeViewStateProvider
             implements ShadeViewStateProvider {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 13167b2..c259782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -71,7 +71,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -455,7 +454,6 @@
     private RemoteInputViewController bindController(
             RemoteInputView view,
             NotificationEntry entry) {
-        mFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true);
         RemoteInputViewControllerImpl viewController = new RemoteInputViewControllerImpl(
                 view,
                 entry,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 6ac702e..8866fd3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.communal.domain.interactor
 
+import android.os.userManager
+import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.communal.data.repository.communalMediaRepository
 import com.android.systemui.communal.data.repository.communalPrefsRepository
 import com.android.systemui.communal.data.repository.communalRepository
@@ -29,6 +31,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.plugins.activityStarter
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.settings.userTracker
@@ -39,6 +42,7 @@
 val Kosmos.communalInteractor by Fixture {
     CommunalInteractor(
         applicationScope = applicationCoroutineScope,
+        broadcastDispatcher = broadcastDispatcher,
         communalRepository = communalRepository,
         widgetRepository = communalWidgetRepository,
         mediaRepository = communalMediaRepository,
@@ -48,6 +52,8 @@
         keyguardInteractor = keyguardInteractor,
         editWidgetsActivityStarter = editWidgetsActivityStarter,
         userTracker = userTracker,
+        activityStarter = activityStarter,
+        userManager = userManager,
         logBuffer = logcatLogBuffer("CommunalInteractor"),
         tableLogBuffer = mock(),
         communalSettingsInteractor = communalSettingsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
index 5dd5073..a45b269 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
@@ -19,12 +19,10 @@
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
 
 val Kosmos.glanceableHubTransitions by
     Kosmos.Fixture {
         GlanceableHubTransitions(
-            bgDispatcher = testDispatcher,
             transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
             communalInteractor = communalInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt
index 00741eb..298c70d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.keyguard.domain.interactor.fromDreamingTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 
@@ -25,5 +26,6 @@
         DreamingToGlanceableHubTransitionViewModel(
             configurationInteractor = configurationInteractor,
             animationFlow = keyguardTransitionAnimationFlow,
+            fromDreamingTransitionInteractor = fromDreamingTransitionInteractor
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
index 5f70a2f..450dcc2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -26,7 +25,6 @@
 @ExperimentalCoroutinesApi
 val Kosmos.dreamingToLockscreenTransitionViewModel by Fixture {
     DreamingToLockscreenTransitionViewModel(
-        keyguardTransitionInteractor = keyguardTransitionInteractor,
         fromDreamingTransitionInteractor = mock(),
         animationFlow = keyguardTransitionAnimationFlow,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 6b604e1..728c67a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.shade.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.model.ShadeMode
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
@@ -60,8 +61,8 @@
 
     override val legacyLockscreenShadeTracking = MutableStateFlow(false)
 
-    private val _isSplitShade = MutableStateFlow(false)
-    override val isSplitShade: StateFlow<Boolean> = _isSplitShade.asStateFlow()
+    private val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
+    override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
 
     @Deprecated("Use ShadeInteractor instead")
     override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) {
@@ -136,8 +137,8 @@
         _legacyShadeExpansion.value = expandedFraction
     }
 
-    override fun setSplitShade(isSplitShade: Boolean) {
-        _isSplitShade.value = isSplitShade
+    override fun setShadeMode(shadeMode: ShadeMode) {
+        _shadeMode.value = shadeMode
     }
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index c013664..de0cc65 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
-import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.dump.dumpManager
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
@@ -56,7 +55,6 @@
         keyguardInteractor = keyguardInteractor,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         shadeInteractor = shadeInteractor,
-        communalInteractor = communalInteractor,
         alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
         aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
         aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
diff --git a/packages/Tethering/OWNERS b/packages/Tethering/OWNERS
deleted file mode 100644
index aa87958..0000000
--- a/packages/Tethering/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/net/OWNERS
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index af47ed2..73584154 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -611,12 +611,12 @@
         if (svcConnTracingEnabled()) {
             logTraceSvcConn("getWindow", "windowId=" + windowId);
         }
+        int displayId = Display.INVALID_DISPLAY;
+        if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+            displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowId(
+                    mSystemSupport.getCurrentUserIdLocked(), windowId);
+        }
         synchronized (mLock) {
-            int displayId = Display.INVALID_DISPLAY;
-            if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
-                displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(
-                        mSystemSupport.getCurrentUserIdLocked(), windowId);
-            }
             ensureWindowsAvailableTimedLocked(displayId);
 
             if (!hasRightsToCurrentUserLocked()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3cbfd42..4be303a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1261,15 +1261,14 @@
             // the computation for performance reasons.
             boolean shouldComputeWindows = false;
             int displayId = event.getDisplayId();
+            final int windowId = event.getWindowId();
+            if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
+                    && displayId == Display.INVALID_DISPLAY) {
+                displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowId(
+                        resolvedUserId, windowId);
+                event.setDisplayId(displayId);
+            }
             synchronized (mLock) {
-                final int windowId = event.getWindowId();
-                if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
-                        && displayId == Display.INVALID_DISPLAY) {
-                    displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(
-                            resolvedUserId, windowId);
-                    event.setDisplayId(displayId);
-                }
-
                 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
                         && displayId != Display.INVALID_DISPLAY
                         && mA11yWindowManager.isTrackingWindowsLocked(displayId)) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index b818150..8c06bc8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -2038,8 +2038,11 @@
      * @param windowId The windowId
      * @return The display ID
      */
-    public int getDisplayIdByUserIdAndWindowIdLocked(int userId, int windowId) {
-        final IBinder windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId);
+    public int getDisplayIdByUserIdAndWindowId(int userId, int windowId) {
+        final IBinder windowToken;
+        synchronized (mLock) {
+            windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId);
+        }
         if (traceWMEnabled()) {
             logTraceWM("getDisplayIdForWindow", "token=" + windowToken);
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index d9e25ef..e13994e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -525,8 +525,9 @@
             mReceivedPointersDown |= pointerFlag;
             mReceivedPointers[pointerId].set(
                     event.getX(pointerIndex), event.getY(pointerIndex), event.getEventTime());
-
-            mPrimaryPointerId = pointerId;
+            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                mPrimaryPointerId = pointerId;
+            }
         }
 
         /**
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 13bc772..ca2a3dd 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -6458,12 +6458,12 @@
                 } else if (response != null) {
                     if (viewId.isVirtualInt()) {
                         ViewNode viewNode = getViewNodeFromContextsLocked(viewId);
-                        if (viewNode != null && viewNode.getCredentialManagerCallback() != null) {
+                        if (viewNode != null && viewNode.getPendingCredentialCallback() != null) {
                             Bundle resultData = new Bundle();
                             resultData.putParcelable(
                                     CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
                                     response);
-                            viewNode.getCredentialManagerCallback().send(SUCCESS_CREDMAN_SELECTOR,
+                            viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR,
                                         resultData);
                         } else {
                             Slog.w(TAG, "View node not found after GetCredentialResponse");
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 b1672ed..8244d20 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -29,6 +29,7 @@
 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;
+import static android.companion.virtualdevice.flags.Flags.virtualCameraServiceDiscovery;
 
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
@@ -111,6 +112,8 @@
 import com.android.server.companion.virtual.camera.VirtualCameraController;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 
+import dalvik.annotation.optimization.FastNative;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.List;
@@ -265,7 +268,7 @@
                 runningAppsChangedCallback,
                 params,
                 DisplayManagerGlobal.getInstance(),
-                Flags.virtualCamera()
+                isVirtualCameraEnabled()
                         ? new VirtualCameraController(params.getDevicePolicy(POLICY_TYPE_CAMERA))
                         : null);
     }
@@ -1535,4 +1538,13 @@
             return mToken;
         }
     }
+
+    private static boolean isVirtualCameraEnabled() {
+        return Flags.virtualCamera() && virtualCameraServiceDiscovery()
+                && nativeVirtualCameraServiceBuildFlagEnabled();
+    }
+
+    // Returns true if virtual_camera service is enabled in this build.
+    @FastNative
+    private static native boolean nativeVirtualCameraServiceBuildFlagEnabled();
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 60bfc63..5e6ff55 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4755,6 +4755,7 @@
                         autofillOptions,
                         contentCaptureOptions,
                         app.getDisabledCompatChanges(),
+                        app.getLoggableCompatChanges(),
                         serializedSystemFontMap,
                         app.getStartElapsedTime(),
                         app.getStartUptime());
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 0ce1407..48daef8 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -711,7 +711,7 @@
                 }
             }
             if (profile != null) {
-                long startTime = SystemClock.currentThreadTimeMillis();
+                long startTime = SystemClock.uptimeMillis();
                 // skip background PSS calculation under the following situations:
                 //  - app is capturing camera imagery
                 //  - app is frozen and we have already collected PSS once.
@@ -721,7 +721,7 @@
                         || mService.isCameraActiveForUid(profile.mApp.uid)
                         || mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED;
                 long pss = skipPSSCollection ? 0 : Debug.getPss(pid, tmp, null);
-                long endTime = SystemClock.currentThreadTimeMillis();
+                long endTime = SystemClock.uptimeMillis();
                 synchronized (mProfilerLock) {
                     if (pss != 0 && profile.getThread() != null
                             && profile.getSetProcState() == procState
@@ -852,7 +852,7 @@
                 }
             }
             if (profile != null) {
-                long startTime = SystemClock.currentThreadTimeMillis();
+                long startTime = SystemClock.uptimeMillis();
                 // skip background RSS calculation under the following situations:
                 //  - app is capturing camera imagery
                 //  - app is frozen and we have already collected RSS once.
@@ -862,7 +862,7 @@
                         || mService.isCameraActiveForUid(profile.mApp.uid)
                         || mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED;
                 long rss = skipRSSCollection ? 0 : Debug.getRss(pid, null);
-                long endTime = SystemClock.currentThreadTimeMillis();
+                long endTime = SystemClock.uptimeMillis();
                 synchronized (mProfilerLock) {
                     if (rss != 0 && profile.getThread() != null
                             && profile.getSetProcState() == procState
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index cd6964e..7f6d62c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1051,7 +1051,7 @@
 
         assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
 
-        postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
+        postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true);
 
         if (startProfiling) {
             mService.mOomAdjProfiler.oomAdjEnded();
@@ -1073,12 +1073,12 @@
 
     @GuardedBy({"mService", "mProcLock"})
     protected void postUpdateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, ActiveUids activeUids,
-            long now, long nowElapsed, long oldTime) {
+            long now, long nowElapsed, long oldTime, boolean doingAll) {
         mNumNonCachedProcs = 0;
         mNumCachedHiddenProcs = 0;
 
         final boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids,
-                oomAdjReason);
+                oomAdjReason, doingAll);
         mNumServiceProcs = mNewNumServiceProcs;
 
         if (mService.mAlwaysFinishActivities) {
@@ -1288,7 +1288,8 @@
 
     @GuardedBy({"mService", "mProcLock"})
     private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
-            final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason) {
+            final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason,
+            boolean doingAll) {
         ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
         final int numLru = lruList.size();
 
@@ -1321,7 +1322,7 @@
             if (!app.isKilledByAm() && app.getThread() != null) {
                 // We don't need to apply the update for the process which didn't get computed
                 if (state.getCompletedAdjSeq() == mAdjSeq) {
-                    applyOomAdjLSP(app, true, now, nowElapsed, oomAdjReason);
+                    applyOomAdjLSP(app, doingAll, now, nowElapsed, oomAdjReason);
                 }
 
                 if (app.isPendingFinishAttach()) {
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index dd75bc0..46bdfe8 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -820,7 +820,7 @@
         computeConnectionsLSP();
 
         assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
-        postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime);
+        postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime, true);
     }
 
     /**
@@ -908,7 +908,7 @@
             }
         }
 
-        postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
+        postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, false);
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 27d6c60..48a9d6a 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2088,8 +2088,10 @@
                     + " with non-zero pid:" + app.getPid());
         }
         app.setDisabledCompatChanges(null);
+        app.setLoggableCompatChanges(null);
         if (mPlatformCompat != null) {
             app.setDisabledCompatChanges(mPlatformCompat.getDisabledChanges(app.info));
+            app.setLoggableCompatChanges(mPlatformCompat.getLoggableChanges(app.info));
         }
         final long startSeq = ++mProcStartSeqCounter;
         app.setStartSeq(startSeq);
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 9fa3a8b..b939089 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -259,6 +259,12 @@
     private long[] mDisabledCompatChanges;
 
     /**
+     * Set of compat changes for the process that are intended to be logged to logcat.
+     */
+    @GuardedBy("mService")
+    private long[] mLoggableCompatChanges;
+
+    /**
      * Who is watching for the death.
      */
     @GuardedBy("mService")
@@ -935,11 +941,21 @@
     }
 
     @GuardedBy("mService")
+    long[] getLoggableCompatChanges() {
+        return mLoggableCompatChanges;
+    }
+
+    @GuardedBy("mService")
     void setDisabledCompatChanges(long[] disabledCompatChanges) {
         mDisabledCompatChanges = disabledCompatChanges;
     }
 
     @GuardedBy("mService")
+    void setLoggableCompatChanges(long[] loggableCompatChanges) {
+        mLoggableCompatChanges = loggableCompatChanges;
+    }
+
+    @GuardedBy("mService")
     void unlinkDeathRecipient() {
         if (mDeathRecipient != null && mThread != null) {
             mThread.asBinder().unlinkToDeath(mDeathRecipient, 0);
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d1bda79..7df5fdd 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -170,6 +170,7 @@
         "pixel_connectivity_gps",
         "pixel_system_sw_video",
         "pixel_watch",
+        "platform_compat",
         "platform_security",
         "pmw",
         "power",
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
index 03acf72..d93ff9d 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -82,13 +82,6 @@
                     Slogf.w(TAG, "No module %s with id %d (HAL AIDL)", name, moduleId);
                     return;
                 }
-                try {
-                    radioModule.setInternalHalCallback();
-                } catch (RemoteException ex) {
-                    Slogf.wtf(TAG, ex, "Broadcast radio module %s with id %d (HAL AIDL) "
-                            + "cannot register HAL callback", name, moduleId);
-                    return;
-                }
                 if (DEBUG) {
                     Slogf.d(TAG, "Loaded broadcast radio module %s with id %d (HAL AIDL)",
                             name, moduleId);
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index 4b3444d..cd86510 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -246,10 +246,6 @@
         return mProperties;
     }
 
-    void setInternalHalCallback() throws RemoteException {
-        mService.setTunerCallback(mHalTunerCallback);
-    }
-
     TunerSession openSession(android.hardware.radio.ITunerCallback userCb)
             throws RemoteException {
         mLogger.logRadioEvent("Open TunerSession");
@@ -257,10 +253,14 @@
         Boolean antennaConnected;
         RadioManager.ProgramInfo currentProgramInfo;
         synchronized (mLock) {
+            boolean isFirstTunerSession = mAidlTunerSessions.isEmpty();
             tunerSession = new TunerSession(this, mService, userCb);
             mAidlTunerSessions.add(tunerSession);
             antennaConnected = mAntennaConnected;
             currentProgramInfo = mCurrentProgramInfo;
+            if (isFirstTunerSession) {
+                mService.setTunerCallback(mHalTunerCallback);
+            }
         }
         // Propagate state to new client.
         // Note: These callbacks are invoked while holding mLock to prevent race conditions
@@ -284,7 +284,6 @@
         synchronized (mLock) {
             tunerSessions = new TunerSession[mAidlTunerSessions.size()];
             mAidlTunerSessions.toArray(tunerSessions);
-            mAidlTunerSessions.clear();
         }
 
         for (TunerSession tunerSession : tunerSessions) {
@@ -402,6 +401,14 @@
             mAidlTunerSessions.remove(tunerSession);
         }
         onTunerSessionProgramListFilterChanged(null);
+        if (mAidlTunerSessions.isEmpty()) {
+            try {
+                mService.unsetTunerCallback();
+            } catch (RemoteException ex) {
+                Slogf.wtf(TAG, ex, "Failed to unregister HAL callback for module %d",
+                        mProperties.getId());
+            }
+        }
     }
 
     // add to mHandler queue
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 9102cfd..79025d0 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Environment;
 import android.text.TextUtils;
 import android.util.LongArray;
@@ -72,7 +73,6 @@
  * been configured.
  */
 final class CompatConfig {
-
     private static final String TAG = "CompatConfig";
     private static final String APP_COMPAT_DATA_DIR = "/data/misc/appcompat";
     private static final String STATIC_OVERRIDES_PRODUCT_DIR = "/product/etc/appcompat";
@@ -149,6 +149,56 @@
     }
 
     /**
+     * Retrieves the set of changes that are intended to be logged. This includes changes that
+     * target the most recent SDK version and are not disabled.
+     *
+     * @param app the app in question
+     * @return a sorted long array of change IDs
+     */
+    long[] getLoggableChanges(ApplicationInfo app) {
+        LongArray loggable = new LongArray(mChanges.size());
+        for (CompatChange c : mChanges.values()) {
+            long changeId = c.getId();
+            boolean isLatestSdk = isChangeTargetingLatestSdk(c, app.targetSdkVersion);
+            if (c.isEnabled(app, mAndroidBuildClassifier) && isLatestSdk) {
+                loggable.add(changeId);
+            }
+        }
+        final long[] sortedChanges = loggable.toArray();
+        Arrays.sort(sortedChanges);
+        return sortedChanges;
+    }
+
+    /**
+     * Whether the change indicated by the given changeId is targeting the latest SDK version.
+     * @param c             the change for which to check the target SDK version
+     * @param appSdkVersion the target sdk version of the app
+     * @return true if the changeId targets the current sdk version or the current development
+     * version.
+     */
+    boolean isChangeTargetingLatestSdk(CompatChange c, int appSdkVersion) {
+        int maxTargetSdk = maxTargetSdkForCompatChange(c) + 1;
+        if (maxTargetSdk <= 0) {
+            // No max target sdk found.
+            return false;
+        }
+
+        return maxTargetSdk == Build.VERSION_CODES.CUR_DEVELOPMENT || maxTargetSdk == appSdkVersion;
+    }
+
+    /**
+     * Retrieves the CompatChange associated with the given changeId. Will return null if the
+     * changeId is not found. Used only for performance improvement purposes, in order to reduce
+     * lookups.
+     *
+     * @param changeId for which to look up the CompatChange
+     * @return the found compat change, or null if not found.
+     */
+    CompatChange getCompatChange(long changeId) {
+        return mChanges.get(changeId);
+    }
+
+    /**
      * Looks up a change ID by name.
      *
      * @param name name of the change to look up
@@ -164,7 +214,7 @@
     }
 
     /**
-     * Checks if a given change is enabled for a given application.
+     * Checks if a given change id is enabled for a given application.
      *
      * @param changeId the ID of the change in question
      * @param app      app to check for
@@ -173,6 +223,18 @@
      */
     boolean isChangeEnabled(long changeId, ApplicationInfo app) {
         CompatChange c = mChanges.get(changeId);
+        return isChangeEnabled(c, app);
+    }
+
+    /**
+     * Checks if a given change is enabled for a given application.
+     *
+     * @param c   the CompatChange in question
+     * @param app the app to check for
+     * @return {@code true} if the change is enabled for this app. Also returns {@code true} if the
+     * change ID is not known, as unknown changes are enabled by default.
+     */
+    boolean isChangeEnabled(CompatChange c, ApplicationInfo app) {
         if (c == null) {
             // we know nothing about this change: default behaviour is enabled.
             return true;
@@ -301,9 +363,21 @@
     /**
      * Returns the maximum SDK version for which this change can be opted in (or -1 if it is not
      * target SDK gated).
+     *
+     * @param changeId the id of the CompatChange to check for the max target sdk
      */
     int maxTargetSdkForChangeIdOptIn(long changeId) {
         CompatChange c = mChanges.get(changeId);
+        return maxTargetSdkForCompatChange(c);
+    }
+
+    /**
+     * Returns the maximum SDK version for which this change can be opted in (or -1 if it is not
+     * target SDK gated).
+     *
+     * @param c the CompatChange to check for the max target sdk
+     */
+    int maxTargetSdkForCompatChange(CompatChange c) {
         if (c != null && c.getEnableSinceTargetSdk() != -1) {
             return c.getEnableSinceTargetSdk() - 1;
         }
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 6cca130..f8fd0a0 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -120,8 +120,16 @@
         reportChangeInternal(changeId, uid, ChangeReporter.STATE_LOGGED);
     }
 
+    /**
+     * Report the change, but skip over the sdk target version check. This can be used to force the
+     * debug logs.
+     *
+     * @param changeId        of the change to report
+     * @param uid             of the user
+     * @param state           of the change - enabled/disabled/logged
+     */
     private void reportChangeInternal(long changeId, int uid, int state) {
-        mChangeReporter.reportChange(uid, changeId, state);
+        mChangeReporter.reportChange(uid, changeId, state, true);
     }
 
     @Override
@@ -164,15 +172,25 @@
     }
 
     /**
-     * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}.
+     * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}. If the provided appInfo
+     * is not null, also reports the change.
+     *
+     * @param changeId of the change to report
+     * @param appInfo  the app to check
      *
      * <p>Does not perform costly permission check.
      */
     public boolean isChangeEnabledInternal(long changeId, ApplicationInfo appInfo) {
-        boolean enabled = isChangeEnabledInternalNoLogging(changeId, appInfo);
+        // Fetch the CompatChange. This is done here instead of in mCompatConfig to avoid multiple
+        // fetches.
+        CompatChange c = mCompatConfig.getCompatChange(changeId);
+
+        boolean enabled = mCompatConfig.isChangeEnabled(c, appInfo);
+        int state = enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED;
         if (appInfo != null) {
-            reportChangeInternal(changeId, appInfo.uid,
-                    enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED);
+            boolean isTargetingLatestSdk =
+                    mCompatConfig.isChangeTargetingLatestSdk(c, appInfo.targetSdkVersion);
+            mChangeReporter.reportChange(appInfo.uid, changeId, state, isTargetingLatestSdk);
         }
         return enabled;
     }
@@ -399,6 +417,19 @@
     }
 
     /**
+     * Retrieves the set of changes that should be logged for a given app. Any change ID not in the
+     * returned array is ignored for logging purposes.
+     *
+     * @param appInfo The app in question
+     * @return A sorted long array of change IDs. We use a primitive array to minimize memory
+     * footprint: Every app process will store this array statically so we aim to reduce
+     * overhead as much as possible.
+     */
+    public long[] getLoggableChanges(ApplicationInfo appInfo) {
+        return mCompatConfig.getLoggableChanges(appInfo);
+    }
+
+    /**
      * Look up a change ID by name.
      *
      * @param name Name of the change to look up
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 8b4e1ff..64cbd54 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1015,12 +1015,15 @@
                 // Infinity means that we want the highest possible refresh rate
                 minRefreshRate = highestRefreshRate;
 
-                if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
-                    // The flag had been turned off, we need to restore the original value
+                if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+                        && displayId == Display.DEFAULT_DISPLAY) {
+                    // The flag has been turned off, we need to restore the original value. We'll
+                    // use the peak refresh rate of the default display.
                     Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
                             highestRefreshRate, cr.getUserId());
                 }
             } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+                    && displayId == Display.DEFAULT_DISPLAY
                     && Math.round(minRefreshRate) == Math.round(highestRefreshRate)) {
                 // The flag has been turned on, we need to upgrade the setting
                 Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
@@ -1033,12 +1036,15 @@
                 // Infinity means that we want the highest possible refresh rate
                 peakRefreshRate = highestRefreshRate;
 
-                if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
-                    // The flag had been turned off, we need to restore the original value
+                if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+                        && displayId == Display.DEFAULT_DISPLAY) {
+                    // The flag has been turned off, we need to restore the original value. We'll
+                    // use the peak refresh rate of the default display.
                     Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
                             highestRefreshRate, cr.getUserId());
                 }
             } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+                    && displayId == Display.DEFAULT_DISPLAY
                     && Math.round(peakRefreshRate) == Math.round(highestRefreshRate)) {
                 // The flag has been turned on, we need to upgrade the setting
                 Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index c6d66db..d997020 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -982,6 +982,18 @@
         }
 
         @Override // Binder call
+        public boolean canStartDreaming(boolean isScreenOn) {
+            checkPermission(android.Manifest.permission.READ_DREAM_STATE);
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return canStartDreamingInternal(isScreenOn);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
         public void testDream(int userId, ComponentName dream) {
             if (dream == null) {
                 throw new IllegalArgumentException("dream must not be null");
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 252ea4b..0ef23e9 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -126,7 +126,7 @@
 
         @Override
         public void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
-            checkCallerIsSystem();
+            isCallerAllowed();
             GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
                     userId);
         }
@@ -154,7 +154,7 @@
         @Override
         @Nullable
         public byte[] getBackupPayload(int userId) {
-            checkCallerIsSystem();
+            isCallerAllowed();
             return mBackupHelper.getBackupPayload(userId);
         }
 
@@ -333,11 +333,13 @@
         return GRAMMATICAL_GENDER_NOT_SPECIFIED;
     }
 
-    private void checkCallerIsSystem() {
+    private void isCallerAllowed() {
         int callingUid = Binder.getCallingUid();
         if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID
                 && callingUid != Process.ROOT_UID) {
-            throw new SecurityException("Caller is not system, shell and root.");
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.CHANGE_CONFIGURATION,
+                    "Caller must be system, shell, root or has CHANGE_CONFIGURATION permission.");
         }
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 996477d..fef5661 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -67,7 +67,6 @@
 import android.annotation.Nullable;
 import android.annotation.UiThread;
 import android.annotation.UserIdInt;
-import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -196,21 +195,16 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 import java.security.InvalidParameterException;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Locale;
 import java.util.Objects;
 import java.util.OptionalInt;
 import java.util.WeakHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
@@ -730,344 +724,9 @@
     private final CopyOnWriteArrayList<InputMethodListListener> mInputMethodListListeners =
             new CopyOnWriteArrayList<>();
 
-    /**
-     * Internal state snapshot when
-     * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
-     *
-     * <p>Calling that IPC endpoint basically means that
-     * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
-     * back in the current IME process shortly, which will also affect what the current IME starts
-     * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
-     * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
-     * logical input session between the client application and the current IME.</p>
-     *
-     * <p>Be careful to not keep strong references to this object forever, which can prevent
-     * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
-     * </p>
-     */
-    private static class StartInputInfo {
-        private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
-        final int mSequenceNumber;
-        final long mTimestamp;
-        final long mWallTime;
-        @UserIdInt
-        final int mImeUserId;
-        @NonNull
-        final IBinder mImeToken;
-        final int mImeDisplayId;
-        @NonNull
-        final String mImeId;
-        @StartInputReason
-        final int mStartInputReason;
-        final boolean mRestarting;
-        @UserIdInt
-        final int mTargetUserId;
-        final int mTargetDisplayId;
-        @Nullable
-        final IBinder mTargetWindow;
-        @NonNull
-        final EditorInfo mEditorInfo;
-        @SoftInputModeFlags
-        final int mTargetWindowSoftInputMode;
-        final int mClientBindSequenceNumber;
-
-        StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
-                @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
-                @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
-                @NonNull EditorInfo editorInfo, @SoftInputModeFlags int targetWindowSoftInputMode,
-                int clientBindSequenceNumber) {
-            mSequenceNumber = sSequenceNumber.getAndIncrement();
-            mTimestamp = SystemClock.uptimeMillis();
-            mWallTime = System.currentTimeMillis();
-            mImeUserId = imeUserId;
-            mImeToken = imeToken;
-            mImeDisplayId = imeDisplayId;
-            mImeId = imeId;
-            mStartInputReason = startInputReason;
-            mRestarting = restarting;
-            mTargetUserId = targetUserId;
-            mTargetDisplayId = targetDisplayId;
-            mTargetWindow = targetWindow;
-            mEditorInfo = editorInfo;
-            mTargetWindowSoftInputMode = targetWindowSoftInputMode;
-            mClientBindSequenceNumber = clientBindSequenceNumber;
-        }
-    }
-
     @GuardedBy("ImfLock.class")
     private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>();
 
-    @VisibleForTesting
-    static final class SoftInputShowHideHistory {
-        private final Entry[] mEntries = new Entry[16];
-        private int mNextIndex = 0;
-        private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
-        static final class Entry {
-            final int mSequenceNumber = sSequenceNumber.getAndIncrement();
-            @Nullable
-            final ClientState mClientState;
-            @SoftInputModeFlags
-            final int mFocusedWindowSoftInputMode;
-            @SoftInputShowHideReason
-            final int mReason;
-            // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
-            final long mTimestamp;
-            final long mWallTime;
-            final boolean mInFullscreenMode;
-            @NonNull
-            final String mFocusedWindowName;
-            @Nullable
-            final EditorInfo mEditorInfo;
-            @NonNull
-            final String mRequestWindowName;
-            @Nullable
-            final String mImeControlTargetName;
-            @Nullable
-            final String mImeTargetNameFromWm;
-            @Nullable
-            final String mImeSurfaceParentName;
-
-            Entry(ClientState client, EditorInfo editorInfo,
-                    String focusedWindowName, @SoftInputModeFlags int softInputMode,
-                    @SoftInputShowHideReason int reason,
-                    boolean inFullscreenMode, String requestWindowName,
-                    @Nullable String imeControlTargetName, @Nullable String imeTargetName,
-                    @Nullable String imeSurfaceParentName) {
-                mClientState = client;
-                mEditorInfo = editorInfo;
-                mFocusedWindowName = focusedWindowName;
-                mFocusedWindowSoftInputMode = softInputMode;
-                mReason = reason;
-                mTimestamp = SystemClock.uptimeMillis();
-                mWallTime = System.currentTimeMillis();
-                mInFullscreenMode = inFullscreenMode;
-                mRequestWindowName = requestWindowName;
-                mImeControlTargetName = imeControlTargetName;
-                mImeTargetNameFromWm = imeTargetName;
-                mImeSurfaceParentName = imeSurfaceParentName;
-            }
-        }
-
-        void addEntry(@NonNull Entry entry) {
-            final int index = mNextIndex;
-            mEntries[index] = entry;
-            mNextIndex = (mNextIndex + 1) % mEntries.length;
-        }
-
-        void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-            final DateTimeFormatter formatter =
-                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
-                            .withZone(ZoneId.systemDefault());
-
-            for (int i = 0; i < mEntries.length; ++i) {
-                final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
-                if (entry == null) {
-                    continue;
-                }
-                pw.print(prefix);
-                pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
-
-                pw.print(prefix);
-                pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
-                        + " (timestamp=" + entry.mTimestamp + ")");
-
-                pw.print(prefix);
-                pw.print("  reason=" + InputMethodDebug.softInputDisplayReasonToString(
-                        entry.mReason));
-                pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
-
-                pw.print(prefix);
-                pw.println("  requestClient=" + entry.mClientState);
-
-                pw.print(prefix);
-                pw.println("  focusedWindowName=" + entry.mFocusedWindowName);
-
-                pw.print(prefix);
-                pw.println("  requestWindowName=" + entry.mRequestWindowName);
-
-                pw.print(prefix);
-                pw.println("  imeControlTargetName=" + entry.mImeControlTargetName);
-
-                pw.print(prefix);
-                pw.println("  imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
-
-                pw.print(prefix);
-                pw.println("  imeSurfaceParentName=" + entry.mImeSurfaceParentName);
-
-                pw.print(prefix);
-                pw.print("  editorInfo:");
-                if (entry.mEditorInfo != null) {
-                    pw.print(" inputType=" + entry.mEditorInfo.inputType);
-                    pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
-                    pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
-                } else {
-                    pw.println(" null");
-                }
-
-                pw.print(prefix);
-                pw.println("  focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
-                        entry.mFocusedWindowSoftInputMode));
-            }
-        }
-    }
-
-    /**
-     * A ring buffer to store the history of {@link StartInputInfo}.
-     */
-    private static final class StartInputHistory {
-        /**
-         * Entry size for non low-RAM devices.
-         *
-         * <p>TODO: Consider to follow what other system services have been doing to manage
-         * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
-         */
-        private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
-
-        /**
-         * Entry size for low-RAM devices.
-         *
-         * <p>TODO: Consider to follow what other system services have been doing to manage
-         * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
-         */
-        private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
-
-        private static int getEntrySize() {
-            if (ActivityManager.isLowRamDeviceStatic()) {
-                return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
-            } else {
-                return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
-            }
-        }
-
-        /**
-         * Backing store for the ring buffer.
-         */
-        private final Entry[] mEntries = new Entry[getEntrySize()];
-
-        /**
-         * An index of {@link #mEntries}, to which next {@link #addEntry(StartInputInfo)} should
-         * write.
-         */
-        private int mNextIndex = 0;
-
-        /**
-         * Recyclable entry to store the information in {@link StartInputInfo}.
-         */
-        private static final class Entry {
-            int mSequenceNumber;
-            long mTimestamp;
-            long mWallTime;
-            @UserIdInt
-            int mImeUserId;
-            @NonNull
-            String mImeTokenString;
-            int mImeDisplayId;
-            @NonNull
-            String mImeId;
-            @StartInputReason
-            int mStartInputReason;
-            boolean mRestarting;
-            @UserIdInt
-            int mTargetUserId;
-            int mTargetDisplayId;
-            @NonNull
-            String mTargetWindowString;
-            @NonNull
-            EditorInfo mEditorInfo;
-            @SoftInputModeFlags
-            int mTargetWindowSoftInputMode;
-            int mClientBindSequenceNumber;
-
-            Entry(@NonNull StartInputInfo original) {
-                set(original);
-            }
-
-            void set(@NonNull StartInputInfo original) {
-                mSequenceNumber = original.mSequenceNumber;
-                mTimestamp = original.mTimestamp;
-                mWallTime = original.mWallTime;
-                mImeUserId = original.mImeUserId;
-                // Intentionally convert to String so as not to keep a strong reference to a Binder
-                // object.
-                mImeTokenString = String.valueOf(original.mImeToken);
-                mImeDisplayId = original.mImeDisplayId;
-                mImeId = original.mImeId;
-                mStartInputReason = original.mStartInputReason;
-                mRestarting = original.mRestarting;
-                mTargetUserId = original.mTargetUserId;
-                mTargetDisplayId = original.mTargetDisplayId;
-                // Intentionally convert to String so as not to keep a strong reference to a Binder
-                // object.
-                mTargetWindowString = String.valueOf(original.mTargetWindow);
-                mEditorInfo = original.mEditorInfo;
-                mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
-                mClientBindSequenceNumber = original.mClientBindSequenceNumber;
-            }
-        }
-
-        /**
-         * Add a new entry and discard the oldest entry as needed.
-         * @param info {@link StartInputInfo} to be added.
-         */
-        void addEntry(@NonNull StartInputInfo info) {
-            final int index = mNextIndex;
-            if (mEntries[index] == null) {
-                mEntries[index] = new Entry(info);
-            } else {
-                mEntries[index].set(info);
-            }
-            mNextIndex = (mNextIndex + 1) % mEntries.length;
-        }
-
-        void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-            final DateTimeFormatter formatter =
-                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
-                            .withZone(ZoneId.systemDefault());
-
-            for (int i = 0; i < mEntries.length; ++i) {
-                final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
-                if (entry == null) {
-                    continue;
-                }
-                pw.print(prefix);
-                pw.println("StartInput #" + entry.mSequenceNumber + ":");
-
-                pw.print(prefix);
-                pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
-                        + " (timestamp=" + entry.mTimestamp + ")"
-                        + " reason="
-                        + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
-                        + " restarting=" + entry.mRestarting);
-
-                pw.print(prefix);
-                pw.print("  imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
-                pw.print(" imeUserId=" + entry.mImeUserId);
-                pw.println(" imeDisplayId=" + entry.mImeDisplayId);
-
-                pw.print(prefix);
-                pw.println("  targetWin=" + entry.mTargetWindowString
-                        + " [" + entry.mEditorInfo.packageName + "]"
-                        + " targetUserId=" + entry.mTargetUserId
-                        + " targetDisplayId=" + entry.mTargetDisplayId
-                        + " clientBindSeq=" + entry.mClientBindSequenceNumber);
-
-                pw.print(prefix);
-                pw.println("  softInputMode=" + InputMethodDebug.softInputModeToString(
-                        entry.mTargetWindowSoftInputMode));
-
-                pw.print(prefix);
-                pw.println("  inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
-                        + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
-                        + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
-                        + " fieldName=" + entry.mEditorInfo.fieldName
-                        + " actionId=" + entry.mEditorInfo.actionId
-                        + " actionLabel=" + entry.mEditorInfo.actionLabel);
-            }
-        }
-    }
-
     @GuardedBy("ImfLock.class")
     @NonNull
     private final StartInputHistory mStartInputHistory = new StartInputHistory();
diff --git a/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
new file mode 100644
index 0000000..3023603
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+
+final class SoftInputShowHideHistory {
+    private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+    private final Entry[] mEntries = new Entry[16];
+    private int mNextIndex = 0;
+
+    static final class Entry {
+        final int mSequenceNumber = sSequenceNumber.getAndIncrement();
+        @Nullable
+        final ClientState mClientState;
+        @WindowManager.LayoutParams.SoftInputModeFlags
+        final int mFocusedWindowSoftInputMode;
+        @SoftInputShowHideReason
+        final int mReason;
+        // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
+        final long mTimestamp;
+        final long mWallTime;
+        final boolean mInFullscreenMode;
+        @NonNull
+        final String mFocusedWindowName;
+        @Nullable
+        final EditorInfo mEditorInfo;
+        @NonNull
+        final String mRequestWindowName;
+        @Nullable
+        final String mImeControlTargetName;
+        @Nullable
+        final String mImeTargetNameFromWm;
+        @Nullable
+        final String mImeSurfaceParentName;
+
+        Entry(ClientState client, EditorInfo editorInfo,
+                String focusedWindowName,
+                @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+                @SoftInputShowHideReason int reason,
+                boolean inFullscreenMode, String requestWindowName,
+                @Nullable String imeControlTargetName, @Nullable String imeTargetName,
+                @Nullable String imeSurfaceParentName) {
+            mClientState = client;
+            mEditorInfo = editorInfo;
+            mFocusedWindowName = focusedWindowName;
+            mFocusedWindowSoftInputMode = softInputMode;
+            mReason = reason;
+            mTimestamp = SystemClock.uptimeMillis();
+            mWallTime = System.currentTimeMillis();
+            mInFullscreenMode = inFullscreenMode;
+            mRequestWindowName = requestWindowName;
+            mImeControlTargetName = imeControlTargetName;
+            mImeTargetNameFromWm = imeTargetName;
+            mImeSurfaceParentName = imeSurfaceParentName;
+        }
+    }
+
+    void addEntry(@NonNull Entry entry) {
+        final int index = mNextIndex;
+        mEntries[index] = entry;
+        mNextIndex = (mNextIndex + 1) % mEntries.length;
+    }
+
+    void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        final DateTimeFormatter formatter =
+                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+                        .withZone(ZoneId.systemDefault());
+
+        for (int i = 0; i < mEntries.length; ++i) {
+            final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
+            if (entry == null) {
+                continue;
+            }
+            pw.print(prefix);
+            pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
+
+            pw.print(prefix);
+            pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+                    + " (timestamp=" + entry.mTimestamp + ")");
+
+            pw.print(prefix);
+            pw.print("  reason=" + InputMethodDebug.softInputDisplayReasonToString(
+                    entry.mReason));
+            pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
+
+            pw.print(prefix);
+            pw.println("  requestClient=" + entry.mClientState);
+
+            pw.print(prefix);
+            pw.println("  focusedWindowName=" + entry.mFocusedWindowName);
+
+            pw.print(prefix);
+            pw.println("  requestWindowName=" + entry.mRequestWindowName);
+
+            pw.print(prefix);
+            pw.println("  imeControlTargetName=" + entry.mImeControlTargetName);
+
+            pw.print(prefix);
+            pw.println("  imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
+
+            pw.print(prefix);
+            pw.println("  imeSurfaceParentName=" + entry.mImeSurfaceParentName);
+
+            pw.print(prefix);
+            pw.print("  editorInfo:");
+            if (entry.mEditorInfo != null) {
+                pw.print(" inputType=" + entry.mEditorInfo.inputType);
+                pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
+                pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
+            } else {
+                pw.println(" null");
+            }
+
+            pw.print(prefix);
+            pw.println("  focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
+                    entry.mFocusedWindowSoftInputMode));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/StartInputHistory.java b/services/core/java/com/android/server/inputmethod/StartInputHistory.java
new file mode 100644
index 0000000..3a39434
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/StartInputHistory.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.StartInputReason;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+/**
+ * A ring buffer to store the history of {@link StartInputInfo}.
+ */
+final class StartInputHistory {
+    /**
+     * Entry size for non low-RAM devices.
+     *
+     * <p>TODO: Consider to follow what other system services have been doing to manage
+     * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+     */
+    private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
+
+    /**
+     * Entry size for low-RAM devices.
+     *
+     * <p>TODO: Consider to follow what other system services have been doing to manage
+     * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+     */
+    private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
+
+    private static int getEntrySize() {
+        if (ActivityManager.isLowRamDeviceStatic()) {
+            return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
+        } else {
+            return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
+        }
+    }
+
+    /**
+     * Backing store for the ring buffer.
+     */
+    private final Entry[] mEntries = new Entry[getEntrySize()];
+
+    /**
+     * An index of {@link #mEntries}, to which next
+     * {@link #addEntry(StartInputInfo)} should
+     * write.
+     */
+    private int mNextIndex = 0;
+
+    /**
+     * Recyclable entry to store the information in {@link StartInputInfo}.
+     */
+    private static final class Entry {
+        int mSequenceNumber;
+        long mTimestamp;
+        long mWallTime;
+        @UserIdInt
+        int mImeUserId;
+        @NonNull
+        String mImeTokenString;
+        int mImeDisplayId;
+        @NonNull
+        String mImeId;
+        @StartInputReason
+        int mStartInputReason;
+        boolean mRestarting;
+        @UserIdInt
+        int mTargetUserId;
+        int mTargetDisplayId;
+        @NonNull
+        String mTargetWindowString;
+        @NonNull
+        EditorInfo mEditorInfo;
+        @WindowManager.LayoutParams.SoftInputModeFlags
+        int mTargetWindowSoftInputMode;
+        int mClientBindSequenceNumber;
+
+        Entry(@NonNull StartInputInfo original) {
+            set(original);
+        }
+
+        void set(@NonNull StartInputInfo original) {
+            mSequenceNumber = original.mSequenceNumber;
+            mTimestamp = original.mTimestamp;
+            mWallTime = original.mWallTime;
+            mImeUserId = original.mImeUserId;
+            // Intentionally convert to String so as not to keep a strong reference to a Binder
+            // object.
+            mImeTokenString = String.valueOf(original.mImeToken);
+            mImeDisplayId = original.mImeDisplayId;
+            mImeId = original.mImeId;
+            mStartInputReason = original.mStartInputReason;
+            mRestarting = original.mRestarting;
+            mTargetUserId = original.mTargetUserId;
+            mTargetDisplayId = original.mTargetDisplayId;
+            // Intentionally convert to String so as not to keep a strong reference to a Binder
+            // object.
+            mTargetWindowString = String.valueOf(original.mTargetWindow);
+            mEditorInfo = original.mEditorInfo;
+            mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
+            mClientBindSequenceNumber = original.mClientBindSequenceNumber;
+        }
+    }
+
+    /**
+     * Add a new entry and discard the oldest entry as needed.
+     *
+     * @param info {@link StartInputInfo} to be added.
+     */
+    void addEntry(@NonNull StartInputInfo info) {
+        final int index = mNextIndex;
+        if (mEntries[index] == null) {
+            mEntries[index] = new Entry(info);
+        } else {
+            mEntries[index].set(info);
+        }
+        mNextIndex = (mNextIndex + 1) % mEntries.length;
+    }
+
+    void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        final DateTimeFormatter formatter =
+                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+                        .withZone(ZoneId.systemDefault());
+
+        for (int i = 0; i < mEntries.length; ++i) {
+            final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
+            if (entry == null) {
+                continue;
+            }
+            pw.print(prefix);
+            pw.println("StartInput #" + entry.mSequenceNumber + ":");
+
+            pw.print(prefix);
+            pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+                    + " (timestamp=" + entry.mTimestamp + ")"
+                    + " reason="
+                    + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
+                    + " restarting=" + entry.mRestarting);
+
+            pw.print(prefix);
+            pw.print("  imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
+            pw.print(" imeUserId=" + entry.mImeUserId);
+            pw.println(" imeDisplayId=" + entry.mImeDisplayId);
+
+            pw.print(prefix);
+            pw.println("  targetWin=" + entry.mTargetWindowString
+                    + " [" + entry.mEditorInfo.packageName + "]"
+                    + " targetUserId=" + entry.mTargetUserId
+                    + " targetDisplayId=" + entry.mTargetDisplayId
+                    + " clientBindSeq=" + entry.mClientBindSequenceNumber);
+
+            pw.print(prefix);
+            pw.println("  softInputMode=" + InputMethodDebug.softInputModeToString(
+                    entry.mTargetWindowSoftInputMode));
+
+            pw.print(prefix);
+            pw.println("  inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
+                    + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
+                    + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
+                    + " fieldName=" + entry.mEditorInfo.fieldName
+                    + " actionId=" + entry.mEditorInfo.actionId
+                    + " actionLabel=" + entry.mEditorInfo.actionLabel);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/StartInputInfo.java b/services/core/java/com/android/server/inputmethod/StartInputInfo.java
new file mode 100644
index 0000000..1cff737
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/StartInputInfo.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+import com.android.internal.inputmethod.IInputMethod;
+import com.android.internal.inputmethod.StartInputReason;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Internal state snapshot when
+ * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
+ *
+ * <p>Calling that IPC endpoint basically means that
+ * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
+ * back in the current IME process shortly, which will also affect what the current IME starts
+ * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
+ * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
+ * logical input session between the client application and the current IME.</p>
+ *
+ * <p>Be careful to not keep strong references to this object forever, which can prevent
+ * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
+ * </p>
+ */
+final class StartInputInfo {
+    private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+    final int mSequenceNumber;
+    final long mTimestamp;
+    final long mWallTime;
+    @UserIdInt
+    final int mImeUserId;
+    @NonNull
+    final IBinder mImeToken;
+    final int mImeDisplayId;
+    @NonNull
+    final String mImeId;
+    @StartInputReason
+    final int mStartInputReason;
+    final boolean mRestarting;
+    @UserIdInt
+    final int mTargetUserId;
+    final int mTargetDisplayId;
+    @Nullable
+    final IBinder mTargetWindow;
+    @NonNull
+    final EditorInfo mEditorInfo;
+    @WindowManager.LayoutParams.SoftInputModeFlags
+    final int mTargetWindowSoftInputMode;
+    final int mClientBindSequenceNumber;
+
+    StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
+            @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
+            @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
+            @NonNull EditorInfo editorInfo,
+            @WindowManager.LayoutParams.SoftInputModeFlags int targetWindowSoftInputMode,
+            int clientBindSequenceNumber) {
+        mSequenceNumber = sSequenceNumber.getAndIncrement();
+        mTimestamp = SystemClock.uptimeMillis();
+        mWallTime = System.currentTimeMillis();
+        mImeUserId = imeUserId;
+        mImeToken = imeToken;
+        mImeDisplayId = imeDisplayId;
+        mImeId = imeId;
+        mStartInputReason = startInputReason;
+        mRestarting = restarting;
+        mTargetUserId = targetUserId;
+        mTargetDisplayId = targetDisplayId;
+        mTargetWindow = targetWindow;
+        mEditorInfo = editorInfo;
+        mTargetWindowSoftInputMode = targetWindowSoftInputMode;
+        mClientBindSequenceNumber = clientBindSequenceNumber;
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b98424c..c38fbda 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -8210,7 +8210,7 @@
                 try {
                     return mTelecomManager.isInManagedCall()
                             || mTelecomManager.isInSelfManagedCall(pkg,
-                            /* hasCrossUserAccess */ true);
+                            UserHandle.ALL);
                 } catch (IllegalStateException ise) {
                     // Telecom is not ready (this is likely early boot), so there are no calls.
                     return false;
@@ -12054,10 +12054,17 @@
         @Override
         public void onServiceAdded(ManagedServiceInfo info) {
             if (lifetimeExtensionRefactor()) {
-                // We explicitly check the status bar permission for the uid in the info object.
-                // We can't use the calling uid here because it's probably always system server.
-                // Note that this will also be true for the shell.
-                info.isSystemUi = getContext().checkPermission(
+                // Generally, only System or System UI should have the permissions to call
+                // registerSystemService.
+                // isCallerSystemOrPhone tells us whether the caller is System. We negate this,
+                // to eliminate cases where the service was added by the system. This leaves
+                // services registered by system server.
+                // To identify system UI, we explicitly check the status bar permission for the
+                // uid in the info object.
+                // We can't use the calling uid here because it belongs to system server.
+                // Note that this will also return true for the shell, but we deem this
+                // acceptable, for the purposes of testing.
+                info.isSystemUi = !isCallerSystemOrPhone() && getContext().checkPermission(
                         android.Manifest.permission.STATUS_BAR_SERVICE, -1, info.uid)
                         == PERMISSION_GRANTED;
             }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index c7ebb3c..c6bb99e 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -2116,6 +2116,18 @@
 
         @RequiresPermission(READ_FRAME_BUFFER)
         @Override
+        public void saveViewCaptureData() {
+            int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
+            if (PERMISSION_GRANTED == status) {
+                forEachViewCaptureWindow(this::dumpViewCaptureDataToWmTrace);
+            } else {
+                Log.w(TAG, "caller lacks permissions to save view capture data");
+            }
+        }
+
+
+        @RequiresPermission(READ_FRAME_BUFFER)
+        @Override
         public void registerDumpCallback(@NonNull IDumpCallback cb) {
             int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
             if (PERMISSION_GRANTED == status) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5974ac8..266418f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4315,6 +4315,7 @@
             boolean allowDuringSetup) {
         if (allowDuringSetup || isUserSetupComplete()) {
             mContext.startActivityAsUser(intent, bundle, handle);
+            dismissKeyboardShortcutsMenu();
         } else {
             Slog.i(TAG, "Not starting activity because user setup is in progress: " + intent);
         }
@@ -4365,6 +4366,7 @@
         if (statusbar != null) {
             statusbar.showRecentApps(triggeredFromAltTab);
         }
+        dismissKeyboardShortcutsMenu();
     }
 
     private void toggleKeyboardShortcutsMenu(int deviceId) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index e0e5548..e3ac35c 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1681,7 +1681,7 @@
                 || (wallpaperController.getWallpaperTarget() != null
                 && wallpaperController.wallpaperTransitionReady());
         if (wallpaperReady && mPendingAnimation != null) {
-            startAnimation();
+            mWindowManagerService.mAnimator.addAfterPrepareSurfacesRunnable(this::startAnimation);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 7052982..877378c 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -113,8 +113,10 @@
 
         // Apply whole display info immediately as is if either:
         // * it is the first display update
+        // * the display doesn't have visible content
         // * shell transitions are disabled or temporary unavailable
         if (displayInfoDiff == DIFF_EVERYTHING
+                || !mDisplayContent.getLastHasContent()
                 || !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
             ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
                     "DeferredDisplayUpdater: applying DisplayInfo immediately");
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e3435d3..282ecc7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1319,7 +1319,7 @@
         for (int i = 0; i < mChildren.size(); i++)  {
             SurfaceControl sc = mChildren.get(i).getSurfaceControl();
             if (sc != null) {
-                t.reparent(sc, mSurfaceControl);
+                t.reparent(sc, getParentingSurfaceControl());
             }
         }
 
@@ -5828,6 +5828,21 @@
                 || supportsSystemDecorations();
     }
 
+    /**
+     * Returns the {@link SurfaceControl} where all the children should be parented on.
+     *
+     * <p> {@link DisplayContent} inserts a RootWrapper leash in the hierarchy above its original
+     * {@link #mSurfaceControl} and then overrides the {@link #mSurfaceControl} to point to the
+     * RootWrapper.
+     * <p> To prevent inconsistent state later where the DAs might get re-parented to the
+     * RootWrapper, this method should be used which returns the correct surface where the
+     * re-parenting should happen.
+     */
+    @Override
+    SurfaceControl getParentingSurfaceControl() {
+        return mWindowingLayer;
+    }
+
     SurfaceControl getWindowingLayer() {
         return mWindowingLayer;
     }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index fd0289e..f2af852 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -716,7 +716,7 @@
 
         // If parent is null, the layer should be placed offscreen so reparent to null. Otherwise,
         // set to the available parent.
-        t.reparent(mSurfaceControl, mParent == null ? null : mParent.getSurfaceControl());
+        t.reparent(mSurfaceControl, mParent == null ? null : mParent.getParentingSurfaceControl());
 
         if (mLastRelativeToLayer != null) {
             t.setRelativeLayer(mSurfaceControl, mLastRelativeToLayer, mLastLayer);
@@ -2907,6 +2907,17 @@
     }
 
     /**
+     * Returns the {@link SurfaceControl} where all the children should be parented on.
+     *
+     * A {@link WindowContainer} might insert intermediate leashes in the hierarchy and hence
+     * {@link #getSurfaceControl} won't return the correct surface where the children should be
+     * re-parented on.
+     */
+    SurfaceControl getParentingSurfaceControl() {
+        return getSurfaceControl();
+    }
+
+    /**
      * Use this method instead of {@link #getPendingTransaction()} if the Transaction should be
      * synchronized with the client.
      *
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 3607ddd..7a710dc 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -40,6 +40,7 @@
         "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
         "com_android_server_ConsumerIrService.cpp",
         "com_android_server_companion_virtual_InputController.cpp",
+        "com_android_server_companion_virtual_VirtualDeviceImpl.cpp",
         "com_android_server_devicepolicy_CryptoTestHelper.cpp",
         "com_android_server_display_DisplayControl.cpp",
         "com_android_server_display_SmallAreaDetectionController.cpp",
@@ -214,6 +215,7 @@
     static_libs: [
         "android.hardware.broadcastradio@common-utils-1x-lib",
         "libaidlcommonsupport",
+        "libvirtualdevicebuildflags",
     ],
 
     product_variables: {
diff --git a/services/core/jni/com_android_server_companion_virtual_VirtualDeviceImpl.cpp b/services/core/jni/com_android_server_companion_virtual_VirtualDeviceImpl.cpp
new file mode 100644
index 0000000..1e6a9db
--- /dev/null
+++ b/services/core/jni/com_android_server_companion_virtual_VirtualDeviceImpl.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android_companion_virtualdevice_build_flags.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <array>
+
+#include "jni.h"
+
+namespace android {
+namespace {
+
+jboolean nativeVirtualCameraServiceBuildFlagEnabled(JNIEnv* env, jobject clazz) {
+    return ::android::companion::virtualdevice::flags::virtual_camera_service_build_flag();
+}
+
+const std::array<JNINativeMethod, 1> kMethods = {
+        {{"nativeVirtualCameraServiceBuildFlagEnabled", "()Z",
+          (void*)nativeVirtualCameraServiceBuildFlagEnabled}},
+};
+
+} // namespace
+
+int register_android_server_companion_virtual_VirtualDeviceImpl(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "com/android/server/companion/virtual/VirtualDeviceImpl",
+                                    kMethods.data(), kMethods.size());
+}
+
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 0936888..6464081 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -65,6 +65,7 @@
 int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env);
 int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
 int register_android_server_companion_virtual_InputController(JNIEnv* env);
+int register_android_server_companion_virtual_VirtualDeviceImpl(JNIEnv* env);
 int register_android_server_app_GameManagerService(JNIEnv* env);
 int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
 int register_com_android_server_display_DisplayControl(JNIEnv* env);
@@ -128,6 +129,7 @@
     register_android_server_stats_pull_StatsPullAtomService(env);
     register_android_server_sensor_SensorService(vm, env);
     register_android_server_companion_virtual_InputController(env);
+    register_android_server_companion_virtual_VirtualDeviceImpl(env);
     register_android_server_app_GameManagerService(env);
     register_com_android_server_wm_TaskFpsCallbackController(env);
     register_com_android_server_display_DisplayControl(env);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2112dae..73d830d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1134,7 +1134,7 @@
         ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE, platformCompat);
         ServiceManager.addService(Context.PLATFORM_COMPAT_NATIVE_SERVICE,
                 new PlatformCompatNative(platformCompat));
-        AppCompatCallbacks.install(new long[0]);
+        AppCompatCallbacks.install(new long[0], new long[0]);
         t.traceEnd();
 
         // FileIntegrityService responds to requests from apps and the system. It needs to run after
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index a33e52f..e5d3153 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -91,8 +91,8 @@
     @Test
     public void testSoftInputShowHideHistoryDump_withNulls_doesntThrow() {
         var writer = new StringWriter();
-        var history = new InputMethodManagerService.SoftInputShowHideHistory();
-        history.addEntry(new InputMethodManagerService.SoftInputShowHideHistory.Entry(
+        var history = new SoftInputShowHideHistory();
+        history.addEntry(new SoftInputShowHideHistory.Entry(
                 null,
                 null,
                 null,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 64076e6..3eced7f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -1631,12 +1631,25 @@
         director.start(sensorManager);
         director.injectSupportedModesByDisplay(supportedModesByDisplay);
 
-        setPeakRefreshRate(Float.POSITIVE_INFINITY);
+        // Disable Smooth Display
+        setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
 
         Vote vote1 = director.getVote(DISPLAY_ID,
                 Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
         Vote vote2 = director.getVote(DISPLAY_ID_2,
                 Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+        // Enable Smooth Display
+        setPeakRefreshRate(Float.POSITIVE_INFINITY);
+
+        vote1 = director.getVote(DISPLAY_ID,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        vote2 = director.getVote(DISPLAY_ID_2,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
         assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0, /* frameRateHigh= */ 130);
         assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0, /* frameRateHigh= */ 140);
     }
@@ -1654,10 +1667,18 @@
         SensorManager sensorManager = createMockSensorManager(lightSensor);
         director.start(sensorManager);
 
-        setPeakRefreshRate(peakRefreshRate);
+        // Disable Smooth Display
+        setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
 
         Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
         assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+        // Enable Smooth Display
+        setPeakRefreshRate(peakRefreshRate);
+
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
                 /* frameRateHigh= */ peakRefreshRate);
     }
 
@@ -1759,11 +1780,23 @@
         director.start(sensorManager);
         director.injectSupportedModesByDisplay(supportedModesByDisplay);
 
-        setMinRefreshRate(Float.POSITIVE_INFINITY);
+        // Disable Force Peak Refresh Rate
+        setMinRefreshRate(0);
 
         Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
         Vote vote2 = director.getVote(DISPLAY_ID_2,
                 Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+        // Enable Force Peak Refresh Rate
+        setMinRefreshRate(Float.POSITIVE_INFINITY);
+
+        vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        vote2 = director.getVote(DISPLAY_ID_2,
+                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
         assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 130,
                 /* frameRateHigh= */ Float.POSITIVE_INFINITY);
         assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 140,
@@ -1783,9 +1816,17 @@
         SensorManager sensorManager = createMockSensorManager(lightSensor);
         director.start(sensorManager);
 
-        setMinRefreshRate(minRefreshRate);
+        // Disable Force Peak Refresh Rate
+        setMinRefreshRate(0);
 
         Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+        // Enable Force Peak Refresh Rate
+        setMinRefreshRate(minRefreshRate);
+
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
         assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ minRefreshRate,
                 /* frameRateHigh= */ Float.POSITIVE_INFINITY);
     }
@@ -1829,6 +1870,58 @@
     }
 
     @Test
+    public void testPeakAndMinRefreshRate_FlagEnabled_DisplayWithOneMode() {
+        when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+                .thenReturn(true);
+        DisplayModeDirector director =
+                new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        Display.Mode[] modes1 = new Display.Mode[] {
+                new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 60),
+                new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 130),
+        };
+        Display.Mode[] modes2 = new Display.Mode[] {
+                new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 60),
+        };
+        SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+        supportedModesByDisplay.put(DISPLAY_ID, modes1);
+        supportedModesByDisplay.put(DISPLAY_ID_2, modes2);
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+        director.start(sensorManager);
+        director.injectSupportedModesByDisplay(supportedModesByDisplay);
+
+        // Disable Force Peak Refresh Rate and Smooth Display
+        setMinRefreshRate(0);
+        setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+        // Even though the highest refresh rate of the second display == the current min refresh
+        // rate == 60, Force Peak Refresh Rate should remain disabled
+        Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        Vote vote2 = director.getVote(DISPLAY_ID_2,
+                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+        // Even though the highest refresh rate of the second display == the current peak refresh
+        // rate == 60, Smooth Display should remain disabled
+        vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        vote2 = director.getVote(DISPLAY_ID_2,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+    }
+
+    @Test
     public void testSensorRegistration() {
         // First, configure brightness zones or DMD won't register for sensor data.
         final FakeDeviceConfig config = mInjector.getDeviceConfig();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index caa0864..a8b792e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -213,7 +213,7 @@
                 any(), any(), any(),
                 any(), any(),
                 any(), any(),
-                any(),
+                any(), any(),
                 anyLong(), anyLong());
 
         final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
@@ -277,7 +277,7 @@
                 null, null,
                 null,
                 null, null, null,
-                null, null,
+                null, null, null,
                 0, 0);
 
         // Sleep until timeout should have triggered
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index a2756ff..0ba74c6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -1413,6 +1413,9 @@
         final BroadcastRecord userPresentRecord2 = makeBroadcastRecord(userPresent);
 
         mImpl.enqueueBroadcastLocked(userPresentRecord1);
+        // Wait for a few ms before sending another broadcast to allow comparing the
+        // enqueue timestamps of these broadcasts.
+        SystemClock.sleep(5);
         mImpl.enqueueBroadcastLocked(userPresentRecord2);
 
         final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
index fcf761f..67be93b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -215,7 +215,7 @@
                 any(), any(), any(),
                 any(), any(),
                 any(), any(),
-                any(),
+                any(), any(),
                 anyLong(), anyLong());
         final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
         r.setPid(myPid());
@@ -263,7 +263,7 @@
                 null, null,
                 null,
                 null, null, null,
-                null, null,
+                null, null, null,
                 0, 0);
         return app;
     }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index e168596..16d05b1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -214,7 +214,7 @@
                 .thenReturn(mA11yWindowInfos.get(0));
         when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(PIP_WINDOWID))
                 .thenReturn(mA11yWindowInfos.get(1));
-        when(mMockA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(USER_ID,
+        when(mMockA11yWindowManager.getDisplayIdByUserIdAndWindowId(USER_ID,
             WINDOWID_ONSECONDDISPLAY)).thenReturn(SECONDARY_DISPLAY_ID);
         when(mMockA11yWindowManager.getWindowListLocked(SECONDARY_DISPLAY_ID))
             .thenReturn(mA11yWindowInfosOnSecondDisplay);
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index ef15f60..36b163e 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -173,6 +173,25 @@
     }
 
     @Test
+    public void testGetLoggableChanges() throws Exception {
+        final long disabledChangeId = 1234L;
+        final long enabledLatestChangeId = 2345L;
+        final long enabledOlderChangeId = 3456L;
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                // Disabled changes should not be logged.
+                .addDisabledChangeWithId(disabledChangeId)
+                // A change targeting the latest sdk should be logged.
+                .addEnableSinceSdkChangeWithId(3, enabledLatestChangeId)
+                // A change targeting an old sdk should not be logged.
+                .addEnableSinceSdkChangeWithId(1, enabledOlderChangeId)
+                .build();
+
+        assertThat(compatConfig.getLoggableChanges(
+                ApplicationInfoBuilder.create().withTargetSdk(3).build()))
+                    .asList().containsExactly(enabledLatestChangeId);
+    }
+
+    @Test
     public void testPackageOverrideEnabled() throws Exception {
         CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
                 .addDisabledChangeWithId(1234L)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 03f2749..26cda65 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -12008,7 +12008,7 @@
 
         // style + self managed call - bypasses block
         when(mTelecomManager.isInSelfManagedCall(
-                r.getSbn().getPackageName(), true)).thenReturn(true);
+                r.getSbn().getPackageName(), UserHandle.ALL)).thenReturn(true);
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
                 r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
 
@@ -12091,7 +12091,7 @@
         // style + self managed call - bypasses block
         mService.clearNotifications();
         reset(mUsageStats);
-        when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), true))
+        when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), UserHandle.ALL))
                 .thenReturn(true);
 
         mService.addEnqueuedNotification(r);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
index 99d354a..b11f9b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -70,6 +70,7 @@
 
     @Before
     public void before() {
+        doReturn(true).when(mDisplayContent).getLastHasContent();
         mockTransitionsController(/* enabled= */ true);
         mockRemoteDisplayChangeController();
     }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index dc504ca..a35a35a 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -153,7 +153,8 @@
             = SystemProperties.getBoolean("persist.debug.time_correction", true);
 
     private static final boolean USE_DEDICATED_HANDLER_THREAD =
-            SystemProperties.getBoolean("persist.debug.use_dedicated_handler_thread", false);
+            SystemProperties.getBoolean("persist.debug.use_dedicated_handler_thread",
+            Flags.useDedicatedHandlerThread());
 
     static final boolean DEBUG = false; // Never submit with true
     static final boolean DEBUG_RESPONSE_STATS = DEBUG || Log.isLoggable(TAG, Log.DEBUG);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 048b1b2..ff4be55 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2797,7 +2797,9 @@
 
     /**
      * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
-     * calls for a given {@code packageName} and {@code userHandle}.
+     * calls for a given {@code packageName} and {@code userHandle}. If UserHandle.ALL or a user
+     * that isn't the calling user is passed in, the caller will need to have granted the ability
+     * to interact across users.
      *
      * @param packageName the package name of the app to check calls for.
      * @param userHandle the user handle to check calls for.
@@ -2816,41 +2818,7 @@
         if (service != null) {
             try {
                 return service.isInSelfManagedCall(packageName, userHandle,
-                        mContext.getOpPackageName(), false);
-            } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
-                e.rethrowFromSystemServer();
-                return false;
-            }
-        } else {
-            throw new IllegalStateException("Telecom service is not present");
-        }
-    }
-
-    /**
-     * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
-     * calls for a given {@code packageName} amongst all users, given that detectForAllUsers is true
-     * and the caller has the ability to interact across users. If detectForAllUsers isn't enabled,
-     * the calls will be checked against the caller.
-     *
-     * @param packageName the package name of the app to check calls for.
-     * @param detectForAllUsers indicates if calls should be detected across all users.
-     * @return {@code true} if there are ongoing calls, {@code false} otherwise.
-     * @throws SecurityException if detectForAllUsers is true and the caller does not grant the
-     * ability to interact across users.
-     * @hide
-     */
-    @SystemApi
-    @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
-    @RequiresPermission(allOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
-            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
-    public boolean isInSelfManagedCall(@NonNull String packageName,
-            boolean detectForAllUsers) {
-        ITelecomService service = getTelecomService();
-        if (service != null) {
-            try {
-                return service.isInSelfManagedCall(packageName, null,
-                        mContext.getOpPackageName(), detectForAllUsers);
+                        mContext.getOpPackageName());
             } catch (RemoteException e) {
                 Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
                 e.rethrowFromSystemServer();
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 302a472..112471b 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -401,7 +401,7 @@
      * @see TelecomServiceImpl#isInSelfManagedCall
      */
     boolean isInSelfManagedCall(String packageName, in UserHandle userHandle,
-        String callingPackage, boolean detectForAllUsers);
+        String callingPackage);
 
     /**
      * @see TelecomServiceImpl#addCall
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 88acbab..a047b97 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -18852,7 +18852,7 @@
      */
     @SystemApi
     @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
-    @RequiresPermission(android.Manifest.permission.DUMP)
+    @RequiresPermission(android.Manifest.permission.READ_DROPBOX_DATA)
     public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag,
             @NonNull EmergencyCallDiagnosticData data) {
         try {
diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
index e69b60b..c349599 100644
--- a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
+++ b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
@@ -26,5 +26,5 @@
 {
     void onQualifiedNetworkTypesChanged(int apnTypes, in int[] qualifiedNetworkTypes);
     void onNetworkValidationRequested(int networkCapability, IIntegerConsumer callback);
-    void onReconnectQualifedNetworkType(int apnTypes, int qualifiedNetworkType);
+    void onReconnectQualifiedNetworkType(int apnTypes, int qualifiedNetworkType);
 }
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index 7bfe04d..f775de6 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -238,7 +238,7 @@
                 @AccessNetworkConstants.RadioAccessNetworkType int qualifiedNetworkType) {
             if (mCallback != null) {
                 try {
-                    mCallback.onReconnectQualifedNetworkType(apnTypes, qualifiedNetworkType);
+                    mCallback.onReconnectQualifiedNetworkType(apnTypes, qualifiedNetworkType);
                 } catch (RemoteException e) {
                     loge("Failed to call onReconnectQualifiedNetworkType. " + e);
                 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 17f91eb..060015b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -40,9 +40,10 @@
 constructor(
     protected val flicker: LegacyFlickerTest,
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+    protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
 ) {
-    protected val tapl: LauncherInstrumentation by lazy {
-        LauncherInstrumentation().also { it.expectedRotationCheckEnabled = true }
+    init {
+        tapl.setExpectedRotationCheckEnabled(true)
     }
 
     private val logTag = this::class.java.simpleName
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 755636a..75284c7 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -43,13 +43,13 @@
 import android.os.test.TestLooper;
 import android.provider.DeviceConfig;
 import android.util.AtomicFile;
-import android.util.LongArrayQueue;
 import android.util.Xml;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.PackageWatchdog.HealthCheckState;