Merge "Refactored NMS to handle user switch via the onUserSwitching method" into main
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 3ab0934..255ec92 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -30,7 +30,7 @@
     name: "framework-minus-apex.ravenwood-base",
     tools: ["hoststubgen"],
     cmd: "$(location hoststubgen) " +
-        "@$(location ravenwood/texts/ravenwood-standard-options.txt) " +
+        "@$(location :ravenwood-standard-options) " +
 
         "--debug-log $(location hoststubgen_framework-minus-apex.log) " +
         "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " +
@@ -42,13 +42,13 @@
         "--gen-input-dump-file $(location hoststubgen_dump.txt) " +
 
         "--in-jar $(location :framework-minus-apex-for-hoststubgen) " +
-        "--policy-override-file $(location ravenwood/texts/framework-minus-apex-ravenwood-policies.txt) " +
-        "--annotation-allowed-classes-file $(location ravenwood/texts/ravenwood-annotation-allowed-classes.txt) ",
+        "--policy-override-file $(location :ravenwood-framework-policies) " +
+        "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) ",
     srcs: [
         ":framework-minus-apex-for-hoststubgen",
-        "ravenwood/texts/framework-minus-apex-ravenwood-policies.txt",
-        "ravenwood/texts/ravenwood-standard-options.txt",
-        "ravenwood/texts/ravenwood-annotation-allowed-classes.txt",
+        ":ravenwood-framework-policies",
+        ":ravenwood-standard-options",
+        ":ravenwood-annotation-allowed-classes",
     ],
     out: [
         "ravenwood.jar",
@@ -118,7 +118,7 @@
     name: "services.core.ravenwood-base",
     tools: ["hoststubgen"],
     cmd: "$(location hoststubgen) " +
-        "@$(location ravenwood/texts/ravenwood-standard-options.txt) " +
+        "@$(location :ravenwood-standard-options) " +
 
         "--debug-log $(location hoststubgen_services.core.log) " +
         "--stats-file $(location hoststubgen_services.core_stats.csv) " +
@@ -130,13 +130,13 @@
         "--gen-input-dump-file $(location hoststubgen_dump.txt) " +
 
         "--in-jar $(location :services.core-for-hoststubgen) " +
-        "--policy-override-file $(location ravenwood/texts/services.core-ravenwood-policies.txt) " +
-        "--annotation-allowed-classes-file $(location ravenwood/texts/ravenwood-annotation-allowed-classes.txt) ",
+        "--policy-override-file $(location :ravenwood-services-policies) " +
+        "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) ",
     srcs: [
         ":services.core-for-hoststubgen",
-        "ravenwood/texts/services.core-ravenwood-policies.txt",
-        "ravenwood/texts/ravenwood-standard-options.txt",
-        "ravenwood/texts/ravenwood-annotation-allowed-classes.txt",
+        ":ravenwood-services-policies",
+        ":ravenwood-standard-options",
+        ":ravenwood-annotation-allowed-classes",
     ],
     out: [
         "ravenwood.jar",
diff --git a/config/Android.bp b/config/Android.bp
index adce203..c9948c3 100644
--- a/config/Android.bp
+++ b/config/Android.bp
@@ -33,7 +33,7 @@
     name: "preloaded-classes",
     src: "preloaded-classes",
     filename: "preloaded-classes",
-    installable: false,
+    no_full_install: true,
 }
 
 filegroup {
diff --git a/core/java/android/app/admin/StringSetPolicyValue.java b/core/java/android/app/admin/PackageSetPolicyValue.java
similarity index 71%
rename from core/java/android/app/admin/StringSetPolicyValue.java
rename to core/java/android/app/admin/PackageSetPolicyValue.java
index 12b11f4..8b253a2 100644
--- a/core/java/android/app/admin/StringSetPolicyValue.java
+++ b/core/java/android/app/admin/PackageSetPolicyValue.java
@@ -28,18 +28,18 @@
 /**
  * @hide
  */
-public final class StringSetPolicyValue extends PolicyValue<Set<String>> {
+public final class PackageSetPolicyValue extends PolicyValue<Set<String>> {
 
-    public StringSetPolicyValue(@NonNull Set<String> value) {
+    public PackageSetPolicyValue(@NonNull Set<String> value) {
         super(value);
         if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            for (String str : value) {
-                PolicySizeVerifier.enforceMaxStringLength(str, "policyValue");
+            for (String packageName : value) {
+                PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
             }
         }
     }
 
-    public StringSetPolicyValue(Parcel source) {
+    public PackageSetPolicyValue(Parcel source) {
         this(readValues(source));
     }
 
@@ -56,7 +56,7 @@
     public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
-        StringSetPolicyValue other = (StringSetPolicyValue) o;
+        PackageSetPolicyValue other = (PackageSetPolicyValue) o;
         return Objects.equals(getValue(), other.getValue());
     }
 
@@ -67,7 +67,7 @@
 
     @Override
     public String toString() {
-        return "StringSetPolicyValue { " + getValue() + " }";
+        return "PackageNameSetPolicyValue { " + getValue() + " }";
     }
 
     @Override
@@ -84,16 +84,16 @@
     }
 
     @NonNull
-    public static final Creator<StringSetPolicyValue> CREATOR =
-            new Creator<StringSetPolicyValue>() {
+    public static final Creator<PackageSetPolicyValue> CREATOR =
+            new Creator<PackageSetPolicyValue>() {
                 @Override
-                public StringSetPolicyValue createFromParcel(Parcel source) {
-                    return new StringSetPolicyValue(source);
+                public PackageSetPolicyValue createFromParcel(Parcel source) {
+                    return new PackageSetPolicyValue(source);
                 }
 
                 @Override
-                public StringSetPolicyValue[] newArray(int size) {
-                    return new StringSetPolicyValue[size];
+                public PackageSetPolicyValue[] newArray(int size) {
+                    return new PackageSetPolicyValue[size];
                 }
             };
 }
diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java
index 7320cea..dede5b5 100644
--- a/core/java/android/app/admin/SystemUpdatePolicy.java
+++ b/core/java/android/app/admin/SystemUpdatePolicy.java
@@ -78,6 +78,11 @@
  *
  * <h3>Developer guide</h3>
  * To learn more, read <a href="{@docRoot}work/dpc/system-updates">Manage system updates</a>.
+ * <p><strong>Note:</strong> <a href="https://source.android.com/docs/core/ota/modular-system">
+ * Google Play system updates</a> (also called Mainline updates) are automatically downloaded
+ * but require a device reboot to be installed. Refer to the mainline section in
+ * <a href="{@docRoot}work/dpc/system-updates#mainline">Manage system
+ * updates</a> for further details.</p>
  *
  * @see DevicePolicyManager#setSystemUpdatePolicy
  * @see DevicePolicyManager#getSystemUpdatePolicy
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index af13011..b070742 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -753,6 +753,9 @@
         @FlaggedApi(Flags.FLAG_SET_NEXT_ATTRIBUTION_SOURCE)
         public @NonNull Builder setNextAttributionSource(@NonNull AttributionSource value) {
             checkNotUsed();
+            if (value == null) {
+                throw new IllegalArgumentException("Null AttributionSource not permitted.");
+            }
             mBuilderFieldsSet |= 0x20;
             mAttributionSourceState.next =
                     new AttributionSourceState[]{value.mAttributionSourceState};
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index fea2c25..9fe0bef 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -45,4 +45,14 @@
     metadata {
       purpose: PURPOSE_BUGFIX
     }
+}
+
+flag{
+    name: "allow_disable_ipsec_loss_detector"
+    namespace: "vcn"
+    description: "Allow disabling IPsec packet loss detector"
+    bug: "336638836"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
 }
\ No newline at end of file
diff --git a/core/java/android/tracing/inputmethod/InputMethodDataSource.java b/core/java/android/tracing/inputmethod/InputMethodDataSource.java
new file mode 100644
index 0000000..5c5ad69
--- /dev/null
+++ b/core/java/android/tracing/inputmethod/InputMethodDataSource.java
@@ -0,0 +1,58 @@
+/*
+ * 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.tracing.inputmethod;
+
+import android.annotation.NonNull;
+import android.tracing.perfetto.DataSource;
+import android.tracing.perfetto.DataSourceInstance;
+import android.tracing.perfetto.StartCallbackArguments;
+import android.tracing.perfetto.StopCallbackArguments;
+import android.util.proto.ProtoInputStream;
+
+/**
+ * @hide
+ */
+public final class InputMethodDataSource
+        extends DataSource<DataSourceInstance, Void, Void> {
+    public static final String DATA_SOURCE_NAME = "android.inputmethod";
+
+    @NonNull
+    private final Runnable mOnStartCallback;
+    @NonNull
+    private final Runnable mOnStopCallback;
+
+    public InputMethodDataSource(@NonNull Runnable onStart, @NonNull Runnable onStop) {
+        super(DATA_SOURCE_NAME);
+        mOnStartCallback = onStart;
+        mOnStopCallback = onStop;
+    }
+
+    @Override
+    public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
+        return new DataSourceInstance(this, instanceIndex) {
+            @Override
+            protected void onStart(StartCallbackArguments args) {
+                mOnStartCallback.run();
+            }
+
+            @Override
+            protected void onStop(StopCallbackArguments args) {
+                mOnStopCallback.run();
+            }
+        };
+    }
+}
diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
index 1afedc1..4530157 100644
--- a/core/java/android/view/ImeBackAnimationController.java
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -134,7 +134,9 @@
 
     @Override
     public void onBackInvoked() {
-        if (!isBackAnimationAllowed()) {
+        if (!isBackAnimationAllowed() || !mIsPreCommitAnimationInProgress) {
+            // play regular hide animation if back-animation is not allowed or if insets control has
+            // been cancelled by the system (this can happen in split screen for example)
             mInsetsController.hide(ime());
             return;
         }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9579614..60ad926 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -40,7 +40,6 @@
 import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
 import static android.view.flags.Flags.enableUseMeasureCacheDuringForceLayout;
 import static android.view.flags.Flags.sensitiveContentAppProtection;
-import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix;
 import static android.view.flags.Flags.toolkitFrameRateBySizeReadOnly;
 import static android.view.flags.Flags.toolkitFrameRateDefaultNormalReadOnly;
 import static android.view.flags.Flags.toolkitFrameRateSmallUsesPercentReadOnly;
@@ -32230,7 +32229,7 @@
 
         void increaseSensitiveViewsCount() {
             if (mSensitiveViewsCount == 0) {
-                mViewRootImpl.notifySensitiveContentAppProtection(true);
+                mViewRootImpl.addSensitiveContentAppProtection();
             }
             mSensitiveViewsCount++;
         }
@@ -32238,11 +32237,7 @@
         void decreaseSensitiveViewsCount() {
             mSensitiveViewsCount--;
             if (mSensitiveViewsCount == 0) {
-                if (sensitiveContentPrematureProtectionRemovedFix()) {
-                    mViewRootImpl.removeSensitiveContentProtectionOnTransactionCommit();
-                } else {
-                    mViewRootImpl.notifySensitiveContentAppProtection(false);
-                }
+                mViewRootImpl.removeSensitiveContentAppProtection();
             }
             if (mSensitiveViewsCount < 0) {
                 Log.wtf(VIEW_LOG_TAG, "mSensitiveViewsCount is negative" + mSensitiveViewsCount);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ca8eaf9..fa57961 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -25,6 +25,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.DragEvent.ACTION_DRAG_LOCATION;
+import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix;
 import static android.view.InputDevice.SOURCE_CLASS_NONE;
 import static android.view.InsetsSource.ID_IME;
 import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
@@ -4244,7 +4245,14 @@
             mReportNextDraw = false;
             mLastReportNextDrawReason = null;
             mActiveSurfaceSyncGroup = null;
-            mHasPendingTransactions = false;
+            if (mHasPendingTransactions) {
+                // TODO: We shouldn't ever actually hit this, it means mPendingTransaction wasn't
+                // merged with a sync group or BLASTBufferQueue before making it to this point
+                // But better a one or two frame flicker than steady-state broken from dropping
+                // whatever is in this transaction
+                mPendingTransaction.apply();
+                mHasPendingTransactions = false;
+            }
             mSyncBuffer = false;
             if (isInWMSRequestedSync()) {
                 mWmsRequestSyncGroup.markSyncReady();
@@ -4331,29 +4339,42 @@
      *   <li>It should only notify service to unblock projection when all sensitive view are
      *   removed from the window.
      * </ol>
+     *
+     * @param enableProtection if true, the protection is enabled for this window.
+     *                         if false, the protection is removed for this window.
      */
-    void notifySensitiveContentAppProtection(boolean showSensitiveContent) {
+    private void applySensitiveContentAppProtection(boolean enableProtection) {
         try {
             if (mSensitiveContentProtectionService == null) {
                 return;
             }
             if (DEBUG_SENSITIVE_CONTENT) {
                 Log.d(TAG, "Notify sensitive content, package=" + mContext.getPackageName()
-                        + ", token=" + getWindowToken() + ", flag=" + showSensitiveContent);
+                        + ", token=" + getWindowToken() + ", flag=" + enableProtection);
             }
             // The window would be blocked during screen share if it shows sensitive content.
             mSensitiveContentProtectionService.setSensitiveContentProtection(
-                    getWindowToken(), mContext.getPackageName(), showSensitiveContent);
+                    getWindowToken(), mContext.getPackageName(), enableProtection);
         } catch (RemoteException ex) {
             Log.e(TAG, "Unable to protect sensitive content during screen share", ex);
         }
     }
 
     /**
-     * Sensitive protection is removed on transaction commit to avoid prematurely removing
-     * the protection.
+     * Add sensitive content protection, when there are one or more visible sensitive views.
      */
-    void removeSensitiveContentProtectionOnTransactionCommit() {
+    void addSensitiveContentAppProtection() {
+        applySensitiveContentAppProtection(true);
+    }
+
+    /**
+     * Remove sensitive content protection, when there is no visible sensitive view.
+     */
+    void removeSensitiveContentAppProtection() {
+        if (!sensitiveContentPrematureProtectionRemovedFix()) {
+            applySensitiveContentAppProtection(false);
+            return;
+        }
         if (DEBUG_SENSITIVE_CONTENT) {
             Log.d(TAG, "Add transaction to remove sensitive content protection, package="
                     + mContext.getPackageName() + ", token=" + getWindowToken());
@@ -4361,7 +4382,7 @@
         Transaction t = new Transaction();
         t.addTransactionCommittedListener(mExecutor, () -> {
             if (mAttachInfo.mSensitiveViewsCount == 0) {
-                notifySensitiveContentAppProtection(false);
+                applySensitiveContentAppProtection(false);
             }
         });
         applyTransactionOnDraw(t);
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 12bd45a..c0d31fa 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -54,7 +54,7 @@
   is_fixed_read_only: true
   metadata {
       purpose: PURPOSE_BUGFIX
-    }
+  }
 }
 
 flag {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0e7de48..a073873 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2461,6 +2461,7 @@
      * @hide
      */
     public boolean hideSoftInputFromView(@NonNull View view, @HideFlags int flags) {
+        checkFocus();
         final boolean isFocusedAndWindowFocused = view.hasWindowFocus() && view.isFocused();
         synchronized (mH) {
             final boolean hasServedByInputMethod = hasServedByInputMethodLocked(view);
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 5c113f8..461eab6 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -18,6 +18,7 @@
 
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
@@ -93,6 +94,19 @@
     @TaskFragmentTransitionType
     public static final int TASK_FRAGMENT_TRANSIT_CHANGE = TRANSIT_CHANGE;
 
+
+    /**
+     * The task fragment drag resize transition used by activity embedding.
+     *
+     * This value is also used in Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE and must not
+     * conflict with other predefined transition types.
+     *
+     * @hide
+     */
+    @WindowManager.TransitionType
+    @TaskFragmentTransitionType
+    public static final int TASK_FRAGMENT_TRANSIT_DRAG_RESIZE = TRANSIT_FIRST_CUSTOM + 17;
+
     /**
      * Introduced a sub set of {@link WindowManager.TransitionType} for the types that are used for
      * TaskFragment transition.
@@ -106,6 +120,7 @@
             TASK_FRAGMENT_TRANSIT_OPEN,
             TASK_FRAGMENT_TRANSIT_CLOSE,
             TASK_FRAGMENT_TRANSIT_CHANGE,
+            TASK_FRAGMENT_TRANSIT_DRAG_RESIZE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface TaskFragmentTransitionType {}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 945164a..4d1b87a 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -120,4 +120,11 @@
     metadata {
         purpose: PURPOSE_BUGFIX
     }
+}
+
+flag {
+    namespace: "windowing_sdk"
+    name: "pip_restore_to_overlay"
+    description: "Restore exit-pip activity back to ActivityEmbedding overlay"
+    bug: "297887697"
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/inputmethod/ImeTracing.java b/core/java/com/android/internal/inputmethod/ImeTracing.java
index ee9c3aa..cd4ccda 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracing.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracing.java
@@ -60,7 +60,9 @@
      */
     public static ImeTracing getInstance() {
         if (sInstance == null) {
-            if (isSystemProcess()) {
+            if (android.tracing.Flags.perfettoIme()) {
+                sInstance = new ImeTracingPerfettoImpl();
+            } else if (isSystemProcess()) {
                 sInstance = new ImeTracingServerImpl();
             } else {
                 sInstance = new ImeTracingClientImpl();
@@ -78,7 +80,7 @@
      * and {@see #IME_TRACING_FROM_IMS}
      * @param where
      */
-    public void sendToService(byte[] protoDump, int source, String where) {
+    protected void sendToService(byte[] protoDump, int source, String where) {
         InputMethodManagerGlobal.startProtoDump(protoDump, source, where,
                 e -> Log.e(TAG, "Exception while sending ime-related dump to server", e));
     }
diff --git a/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java
new file mode 100644
index 0000000..91b80dd
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java
@@ -0,0 +1,178 @@
+/*
+ * 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.internal.inputmethod;
+
+import static android.tracing.perfetto.DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.internal.perfetto.protos.Inputmethodeditor.InputMethodClientsTraceProto;
+import android.internal.perfetto.protos.Inputmethodeditor.InputMethodManagerServiceTraceProto;
+import android.internal.perfetto.protos.Inputmethodeditor.InputMethodServiceTraceProto;
+import android.internal.perfetto.protos.TracePacketOuterClass.TracePacket;
+import android.internal.perfetto.protos.WinscopeExtensionsImplOuterClass.WinscopeExtensionsImpl;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.tracing.inputmethod.InputMethodDataSource;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.util.proto.ProtoOutputStream;
+import android.view.inputmethod.InputMethodManager;
+
+import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * An implementation of {@link ImeTracing} for perfetto tracing.
+ */
+final class ImeTracingPerfettoImpl extends ImeTracing {
+    private final AtomicInteger mTracingSessionsCount = new AtomicInteger(0);
+    private final AtomicBoolean mIsClientDumpInProgress = new AtomicBoolean(false);
+    private final AtomicBoolean mIsServiceDumpInProgress = new AtomicBoolean(false);
+    private final AtomicBoolean mIsManagerServiceDumpInProgress = new AtomicBoolean(false);
+    private final InputMethodDataSource mDataSource = new InputMethodDataSource(
+            mTracingSessionsCount::incrementAndGet,
+            mTracingSessionsCount::decrementAndGet);
+
+    ImeTracingPerfettoImpl() {
+        Producer.init(InitArguments.DEFAULTS);
+        mDataSource.register(
+                new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT));
+    }
+
+
+    @Override
+    public void triggerClientDump(String where, InputMethodManager immInstance,
+            @Nullable byte[] icProto) {
+        if (!isEnabled() || !isAvailable()) {
+            return;
+        }
+
+        if (!mIsClientDumpInProgress.compareAndSet(false, true)) {
+            return;
+        }
+
+        if (immInstance == null) {
+            return;
+        }
+
+        try {
+            Trace.beginSection("inputmethod_client_dump");
+            mDataSource.trace((ctx) -> {
+                final ProtoOutputStream os = ctx.newTracePacket();
+                os.write(TracePacket.TIMESTAMP, SystemClock.elapsedRealtimeNanos());
+                final long tokenWinscopeExtensions =
+                        os.start(TracePacket.WINSCOPE_EXTENSIONS);
+                final long tokenExtensionsField =
+                        os.start(WinscopeExtensionsImpl.INPUTMETHOD_CLIENTS);
+                os.write(InputMethodClientsTraceProto.WHERE, where);
+                final long tokenClient =
+                        os.start(InputMethodClientsTraceProto.CLIENT);
+                immInstance.dumpDebug(os, icProto);
+                os.end(tokenClient);
+                os.end(tokenExtensionsField);
+                os.end(tokenWinscopeExtensions);
+            });
+        } finally {
+            mIsClientDumpInProgress.set(false);
+            Trace.endSection();
+        }
+    }
+
+    @Override
+    public void triggerServiceDump(String where,
+            @NonNull ServiceDumper dumper, @Nullable byte[] icProto) {
+        if (!isEnabled() || !isAvailable()) {
+            return;
+        }
+
+        if (!mIsServiceDumpInProgress.compareAndSet(false, true)) {
+            return;
+        }
+
+        try {
+            Trace.beginSection("inputmethod_service_dump");
+            mDataSource.trace((ctx) -> {
+                final ProtoOutputStream os = ctx.newTracePacket();
+                os.write(TracePacket.TIMESTAMP, SystemClock.elapsedRealtimeNanos());
+                final long tokenWinscopeExtensions =
+                        os.start(TracePacket.WINSCOPE_EXTENSIONS);
+                final long tokenExtensionsField =
+                        os.start(WinscopeExtensionsImpl.INPUTMETHOD_SERVICE);
+                os.write(InputMethodServiceTraceProto.WHERE, where);
+                dumper.dumpToProto(os, icProto);
+                os.end(tokenExtensionsField);
+                os.end(tokenWinscopeExtensions);
+            });
+        } finally {
+            mIsServiceDumpInProgress.set(false);
+            Trace.endSection();
+        }
+    }
+
+    @Override
+    public void triggerManagerServiceDump(@NonNull String where, @NonNull ServiceDumper dumper) {
+        if (!isEnabled() || !isAvailable()) {
+            return;
+        }
+
+        if (!mIsManagerServiceDumpInProgress.compareAndSet(false, true)) {
+            return;
+        }
+
+        try {
+            Trace.beginSection("inputmethod_manager_service_dump");
+            mDataSource.trace((ctx) -> {
+                final ProtoOutputStream os = ctx.newTracePacket();
+                os.write(TracePacket.TIMESTAMP, SystemClock.elapsedRealtimeNanos());
+                final long tokenWinscopeExtensions =
+                        os.start(TracePacket.WINSCOPE_EXTENSIONS);
+                final long tokenExtensionsField =
+                        os.start(WinscopeExtensionsImpl.INPUTMETHOD_MANAGER_SERVICE);
+                os.write(InputMethodManagerServiceTraceProto.WHERE, where);
+                dumper.dumpToProto(os, null);
+                os.end(tokenExtensionsField);
+                os.end(tokenWinscopeExtensions);
+            });
+        } finally {
+            mIsManagerServiceDumpInProgress.set(false);
+            Trace.endSection();
+        }
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return mTracingSessionsCount.get() > 0;
+    }
+
+    @Override
+    public void startTrace(@Nullable PrintWriter pw) {
+        // Intentionally left empty. Tracing start/stop is managed through Perfetto.
+    }
+
+    @Override
+    public void stopTrace(@Nullable PrintWriter pw) {
+        // Intentionally left empty. Tracing start/stop is managed through Perfetto.
+    }
+
+    @Override
+    public void addToBuffer(ProtoOutputStream proto, int source) {
+        // Intentionally left empty. Only used for legacy tracing.
+    }
+}
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index f2d2c1b..6ffa826 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -134,10 +134,12 @@
     public static final int CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK = 99;
     public static final int CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK = 100;
     public static final int CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK = 101;
+    public static final int CUJ_LAUNCHER_PRIVATE_SPACE_LOCK = 102;
+    public static final int CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK = 103;
 
     // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
     @VisibleForTesting
-    static final int LAST_CUJ = CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK;
+    static final int LAST_CUJ = CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK;
 
     /** @hide */
     @IntDef({
@@ -230,7 +232,9 @@
             CUJ_LAUNCHER_TASKBAR_ALL_APPS_SEARCH_BACK,
             CUJ_LAUNCHER_WIDGET_PICKER_CLOSE_BACK,
             CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK,
-            CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK
+            CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK,
+            CUJ_LAUNCHER_PRIVATE_SPACE_LOCK,
+            CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -335,6 +339,8 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WIDGET_PICKER_SEARCH_BACK;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_PRIVATE_SPACE_LOCK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_PRIVATE_SPACE_LOCK;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_PRIVATE_SPACE_UNLOCK;
     }
 
     private Cuj() {
@@ -533,6 +539,10 @@
                 return "LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK";
             case CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK:
                 return "LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK";
+            case CUJ_LAUNCHER_PRIVATE_SPACE_LOCK:
+                return "LAUNCHER_PRIVATE_SPACE_LOCK";
+            case CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK:
+                return "LAUNCHER_PRIVATE_SPACE_UNLOCK";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 66b0158..0734e68 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -886,9 +886,16 @@
             cellState.activationAnimator.cancel();
         }
         AnimatorSet animatorSet = new AnimatorSet();
+
+        // When running the line end animation (see doc for createLineEndAnimation), if cell is in:
+        // - activate state - use finger position at the time of hit detection
+        // - deactivate state - use current position where the end was last during initial animation
+        // Note that deactivate state will only come if mKeepDotActivated is themed true.
+        final float startX = activate == CELL_ACTIVATE ? mInProgressX : cellState.lineEndX;
+        final float startY = activate == CELL_ACTIVATE ? mInProgressY : cellState.lineEndY;
         AnimatorSet.Builder animatorSetBuilder = animatorSet
                 .play(createLineDisappearingAnimation())
-                .with(createLineEndAnimation(cellState, mInProgressX, mInProgressY,
+                .with(createLineEndAnimation(cellState, startX, startY,
                         getCenterXForColumn(cell.column), getCenterYForRow(cell.row)));
         if (mDotSize != mDotSizeActivated) {
             animatorSetBuilder.with(createDotRadiusAnimation(cellState));
diff --git a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
index c00ebe4..57bbb1c 100644
--- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
+++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
@@ -241,6 +241,23 @@
         });
     }
 
+    @Test
+    public void testOnBackInvokedHidesImeEvenIfInsetsControlCancelled() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            // start back gesture
+            WindowInsetsAnimationControlListener animationControlListener = startBackGesture();
+
+            // simulate ImeBackAnimationController not receiving control (e.g. due to split screen)
+            animationControlListener.onCancelled(mWindowInsetsAnimationController);
+
+            // commit back gesture
+            mBackAnimationController.onBackInvoked();
+
+            // verify that InsetsController#hide is called
+            verify(mInsetsController, times(1)).hide(ime());
+        });
+    }
+
     private WindowInsetsAnimationControlListener startBackGesture() {
         // start back gesture
         mBackAnimationController.onBackStarted(new BackEvent(0f, 0f, 0f, EDGE_LEFT));
diff --git a/data/keyboards/Android.bp b/data/keyboards/Android.bp
index e62678f..423b55b 100644
--- a/data/keyboards/Android.bp
+++ b/data/keyboards/Android.bp
@@ -33,7 +33,7 @@
     srcs: [
         "*.kl",
     ],
-    installable: false,
+    no_full_install: true,
 }
 
 prebuilt_usr_keychars {
@@ -41,7 +41,7 @@
     srcs: [
         "*.kcm",
     ],
-    installable: false,
+    no_full_install: true,
 }
 
 prebuilt_usr_idc {
@@ -49,5 +49,5 @@
     srcs: [
         "*.idc",
     ],
-    installable: false,
+    no_full_install: true,
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 59092d4..14388a6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -28,6 +28,7 @@
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE;
+import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_DRAG_RESIZE;
 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN;
 import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
@@ -850,6 +851,14 @@
             Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId);
             return;
         }
+
+        if (!parentInfo.isVisible()) {
+            // Only making the TaskContainer invisible and drops the other info, and perform the
+            // update when the next time the Task becomes visible.
+            taskContainer.setIsVisible(false);
+            return;
+        }
+
         // Checks if container should be updated before apply new parentInfo.
         final boolean shouldUpdateContainer = taskContainer.shouldUpdateContainer(parentInfo);
         taskContainer.updateTaskFragmentParentInfo(parentInfo);
@@ -3243,6 +3252,7 @@
         synchronized (mLock) {
             final TransactionRecord transactionRecord =
                     mTransactionManager.startNewTransaction();
+            transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_DRAG_RESIZE);
             final WindowContainerTransaction wct = transactionRecord.getTransaction();
             final TaskContainer taskContainer = mTaskContainers.get(taskId);
             if (taskContainer != null) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 67d34c7..a683738 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -151,6 +151,10 @@
         return mIsVisible;
     }
 
+    void setIsVisible(boolean visible) {
+        mIsVisible = visible;
+    }
+
     boolean hasDirectActivity() {
         return mHasDirectActivity;
     }
@@ -185,13 +189,15 @@
     boolean shouldUpdateContainer(@NonNull TaskFragmentParentInfo info) {
         final Configuration configuration = info.getConfiguration();
 
-        return info.isVisible()
-                // No need to update presentation in PIP until the Task exit PIP.
-                && !isInPictureInPicture(configuration)
-                // If the task properties equals regardless of starting position, don't need to
-                // update the container.
-                && (mConfiguration.diffPublicOnly(configuration) != 0
-                || mDisplayId != info.getDisplayId());
+        if (isInPictureInPicture(configuration)) {
+            // No need to update presentation in PIP until the Task exit PIP.
+            return false;
+        }
+
+        // If the task properties equals regardless of starting position, don't
+        // need to update the container.
+        return mConfiguration.diffPublicOnly(configuration) != 0
+                || mDisplayId != info.getDisplayId();
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index fab298d..049a9e2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -580,7 +580,7 @@
         final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties();
         final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(
                 new Configuration(taskProperties.getConfiguration()), taskProperties.getDisplayId(),
-                false /* visible */, false /* hasDirectActivity */, null /* decorSurface */);
+                true /* visible */, false /* hasDirectActivity */, null /* decorSurface */);
 
         mSplitController.onTaskFragmentParentInfoChanged(mTransaction, TASK_ID, parentInfo);
 
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index a525877..7d86ec2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -1619,6 +1619,48 @@
         verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any());
     }
 
+    @Test
+    public void testTaskFragmentParentInfoChanged() {
+        // Making a split
+        final Activity secondaryActivity = createMockActivity();
+        addSplitTaskFragments(mActivity, secondaryActivity, false /* clearTop */);
+
+        // Updates the parent info.
+        final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+        final Configuration configuration = new Configuration();
+        final TaskFragmentParentInfo originalInfo = new TaskFragmentParentInfo(configuration,
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                null /* decorSurface */);
+        mSplitController.onTaskFragmentParentInfoChanged(mock(WindowContainerTransaction.class),
+                TASK_ID, originalInfo);
+        assertTrue(taskContainer.isVisible());
+
+        // Making a public configuration change while the Task is invisible.
+        configuration.densityDpi += 100;
+        final TaskFragmentParentInfo invisibleInfo = new TaskFragmentParentInfo(configuration,
+                DEFAULT_DISPLAY, false /* visible */, false /* hasDirectActivity */,
+                null /* decorSurface */);
+        mSplitController.onTaskFragmentParentInfoChanged(mock(WindowContainerTransaction.class),
+                TASK_ID, invisibleInfo);
+
+        // Ensure the TaskContainer is inivisible, but the configuration is not updated.
+        assertFalse(taskContainer.isVisible());
+        assertTrue(taskContainer.getTaskFragmentParentInfo().getConfiguration().diffPublicOnly(
+                configuration) > 0);
+
+        // Updates when Task to become visible
+        final TaskFragmentParentInfo visibleInfo = new TaskFragmentParentInfo(configuration,
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                null /* decorSurface */);
+        mSplitController.onTaskFragmentParentInfoChanged(mock(WindowContainerTransaction.class),
+                TASK_ID, visibleInfo);
+
+        // Ensure the Task is visible and configuration is updated.
+        assertTrue(taskContainer.isVisible());
+        assertFalse(taskContainer.getTaskFragmentParentInfo().getConfiguration().diffPublicOnly(
+                configuration) > 0);
+    }
+
     /** Creates a mock activity in the organizer process. */
     private Activity createMockActivity() {
         return createMockActivity(TASK_ID);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index d44033c..a426b20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -26,6 +26,7 @@
 import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
 
 import android.animation.Animator;
 import android.animation.ValueAnimator;
@@ -190,6 +191,10 @@
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters(
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
+        if (info.getType() == TRANSIT_TASK_FRAGMENT_DRAG_RESIZE) {
+            // Jump cut for AE drag resizing because the content is veiled.
+            return new ArrayList<>();
+        }
         boolean isChangeTransition = false;
         for (TransitionInfo.Change change : info.getChanges()) {
             if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 1f9358e..d6b9d34 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -22,6 +22,7 @@
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
 
 import static com.android.wm.shell.transition.DefaultTransitionHandler.isSupportedOverrideAnimation;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
 
 import static java.util.Objects.requireNonNull;
 
@@ -90,6 +91,12 @@
 
     /** Whether ActivityEmbeddingController should animate this transition. */
     public boolean shouldAnimate(@NonNull TransitionInfo info) {
+        if (info.getType() == TRANSIT_TASK_FRAGMENT_DRAG_RESIZE) {
+            // The TRANSIT_TASK_FRAGMENT_DRAG_RESIZE type happens when the user drags the
+            // interactive divider to resize the split containers. The content is veiled, so we will
+            // handle the transition with a jump cut.
+            return true;
+        }
         boolean containsEmbeddingChange = false;
         for (TransitionInfo.Change change : info.getChanges()) {
             if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index a87116e..607a3b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -185,7 +185,7 @@
                 nextTarget = snapAlgorithm.getDismissStartTarget();
             }
             if (nextTarget != null) {
-                mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), nextTarget);
+                mSplitLayout.snapToTarget(mSplitLayout.getDividerPosition(), nextTarget);
                 return true;
             }
             return super.performAccessibilityAction(host, action, args);
@@ -345,9 +345,9 @@
                     mMoving = true;
                 }
                 if (mMoving) {
-                    final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
+                    final int position = mSplitLayout.getDividerPosition() + touchPos - mStartPos;
                     mLastDraggingPosition = position;
-                    mSplitLayout.updateDivideBounds(position);
+                    mSplitLayout.updateDividerBounds(position);
                 }
                 break;
             case MotionEvent.ACTION_UP:
@@ -363,7 +363,7 @@
                 final float velocity = isLeftRightSplit
                         ? mVelocityTracker.getXVelocity()
                         : mVelocityTracker.getYVelocity();
-                final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
+                final int position = mSplitLayout.getDividerPosition() + touchPos - mStartPos;
                 final DividerSnapAlgorithm.SnapTarget snapTarget =
                         mSplitLayout.findSnapTarget(position, velocity, false /* hardDismiss */);
                 mSplitLayout.snapToTarget(position, snapTarget);
@@ -472,12 +472,12 @@
         mInteractive = interactive;
         mHideHandle = hideHandle;
         if (!mInteractive && mHideHandle && mMoving) {
-            final int position = mSplitLayout.getDividePosition();
-            mSplitLayout.flingDividePosition(
+            final int position = mSplitLayout.getDividerPosition();
+            mSplitLayout.flingDividerPosition(
                     mLastDraggingPosition,
                     position,
                     mSplitLayout.FLING_RESIZE_DURATION,
-                    () -> mSplitLayout.setDividePosition(position, true /* applyLayoutChange */));
+                    () -> mSplitLayout.setDividerPosition(position, true /* applyLayoutChange */));
             mMoving = false;
         }
         releaseTouching();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 6b2d544..2ea32f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -78,7 +78,7 @@
 
 /**
  * Records and handles layout of splits. Helps to calculate proper bounds when configuration or
- * divide position changes.
+ * divider position changes.
  */
 public final class SplitLayout implements DisplayInsetsController.OnInsetsChangedListener {
     private static final String TAG = "SplitLayout";
@@ -278,7 +278,7 @@
         return mSplitWindowManager == null ? null : mSplitWindowManager.getSurfaceControl();
     }
 
-    int getDividePosition() {
+    int getDividerPosition() {
         return mDividerPosition;
     }
 
@@ -489,20 +489,20 @@
     public void setDividerAtBorder(boolean start) {
         final int pos = start ? mDividerSnapAlgorithm.getDismissStartTarget().position
                 : mDividerSnapAlgorithm.getDismissEndTarget().position;
-        setDividePosition(pos, false /* applyLayoutChange */);
+        setDividerPosition(pos, false /* applyLayoutChange */);
     }
 
     /**
      * Updates bounds with the passing position. Usually used to update recording bounds while
      * performing animation or dragging divider bar to resize the splits.
      */
-    void updateDivideBounds(int position) {
+    void updateDividerBounds(int position) {
         updateBounds(position);
         mSplitLayoutHandler.onLayoutSizeChanging(this, mSurfaceEffectPolicy.mParallaxOffset.x,
                 mSurfaceEffectPolicy.mParallaxOffset.y);
     }
 
-    void setDividePosition(int position, boolean applyLayoutChange) {
+    void setDividerPosition(int position, boolean applyLayoutChange) {
         mDividerPosition = position;
         updateBounds(mDividerPosition);
         if (applyLayoutChange) {
@@ -511,14 +511,14 @@
     }
 
     /**
-     * Updates divide position and split bounds base on the ratio within root bounds. Falls back
+     * Updates divider position and split bounds base on the ratio within root bounds. Falls back
      * to middle position if the provided SnapTarget is not supported.
      */
     public void setDivideRatio(@PersistentSnapPosition int snapPosition) {
         final DividerSnapAlgorithm.SnapTarget snapTarget = mDividerSnapAlgorithm.findSnapTarget(
                 snapPosition);
 
-        setDividePosition(snapTarget != null
+        setDividerPosition(snapTarget != null
                 ? snapTarget.position
                 : mDividerSnapAlgorithm.getMiddleTarget().position,
                 false /* applyLayoutChange */);
@@ -546,24 +546,24 @@
     }
 
     /**
-     * Sets new divide position and updates bounds correspondingly. Notifies listener if the new
+     * Sets new divider position and updates bounds correspondingly. Notifies listener if the new
      * target indicates dismissing split.
      */
     public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
         switch (snapTarget.snapPosition) {
             case SNAP_TO_START_AND_DISMISS:
-                flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
+                flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
                         () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
                                 EXIT_REASON_DRAG_DIVIDER));
                 break;
             case SNAP_TO_END_AND_DISMISS:
-                flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
+                flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
                         () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
                                 EXIT_REASON_DRAG_DIVIDER));
                 break;
             default:
-                flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
-                        () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */));
+                flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
+                        () -> setDividerPosition(snapTarget.position, true /* applyLayoutChange */));
                 break;
         }
     }
@@ -615,19 +615,19 @@
     public void flingDividerToDismiss(boolean toEnd, int reason) {
         final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position
                 : mDividerSnapAlgorithm.getDismissStartTarget().position;
-        flingDividePosition(getDividePosition(), target, FLING_EXIT_DURATION,
+        flingDividerPosition(getDividerPosition(), target, FLING_EXIT_DURATION,
                 () -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason));
     }
 
     /** Fling divider from current position to center position. */
     public void flingDividerToCenter() {
         final int pos = mDividerSnapAlgorithm.getMiddleTarget().position;
-        flingDividePosition(getDividePosition(), pos, FLING_ENTER_DURATION,
-                () -> setDividePosition(pos, true /* applyLayoutChange */));
+        flingDividerPosition(getDividerPosition(), pos, FLING_ENTER_DURATION,
+                () -> setDividerPosition(pos, true /* applyLayoutChange */));
     }
 
     @VisibleForTesting
-    void flingDividePosition(int from, int to, int duration,
+    void flingDividerPosition(int from, int to, int duration,
             @Nullable Runnable flingFinishedCallback) {
         if (from == to) {
             if (flingFinishedCallback != null) {
@@ -647,7 +647,7 @@
                 .setDuration(duration);
         mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
         mDividerFlingAnimator.addUpdateListener(
-                animation -> updateDivideBounds((int) animation.getAnimatedValue()));
+                animation -> updateDividerBounds((int) animation.getAnimatedValue()));
         mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index cf3ad42..713d04bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -194,6 +194,10 @@
         return mHideSizeCompatRestartButtonTolerance;
     }
 
+    int getDefaultHideRestartButtonTolerance() {
+        return MAX_PERCENTAGE_VAL;
+    }
+
     boolean getHasSeenLetterboxEducation(int userId) {
         return mLetterboxEduSharedPreferences
                 .getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 4e5c2fa..f195f95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -20,11 +20,11 @@
 import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
 import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
 import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.AppCompatTaskInfo;
 import android.app.CameraCompatTaskInfo.CameraCompatControlState;
 import android.app.TaskInfo;
 import android.content.Context;
@@ -219,14 +219,30 @@
 
     @VisibleForTesting
     boolean shouldShowSizeCompatRestartButton(@NonNull TaskInfo taskInfo) {
-        if (!Flags.allowHideScmButton()) {
+        // Always show button if display is phone sized.
+        if (!Flags.allowHideScmButton() || taskInfo.configuration.smallestScreenWidthDp
+                < LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP) {
             return true;
         }
-        final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
-        final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
-        final int letterboxArea = computeArea(appCompatTaskInfo.topActivityLetterboxWidth,
-                appCompatTaskInfo.topActivityLetterboxHeight);
-        final int taskArea = computeArea(taskBounds.width(), taskBounds.height());
+
+        final int letterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth;
+        final int letterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight;
+        final Rect stableBounds = getTaskStableBounds();
+        final int appWidth = stableBounds.width();
+        final int appHeight = stableBounds.height();
+        // App is floating, should always show restart button.
+        if (appWidth > letterboxWidth && appHeight > letterboxHeight) {
+            return true;
+        }
+        // If app fills the width of the display, don't show restart button (for landscape apps)
+        // if device has a custom tolerance value.
+        if (mHideScmTolerance != mCompatUIConfiguration.getDefaultHideRestartButtonTolerance()
+                && appWidth == letterboxWidth)  {
+            return false;
+        }
+
+        final int letterboxArea = letterboxWidth * letterboxHeight;
+        final int taskArea = appWidth * appHeight;
         if (letterboxArea == 0 || taskArea == 0) {
             return false;
         }
@@ -234,13 +250,6 @@
         return percentageAreaOfLetterboxInTask < mHideScmTolerance;
     }
 
-    private int computeArea(int width, int height) {
-        if (width == 0 || height == 0) {
-            return 0;
-        }
-        return width * height;
-    }
-
     private void updateVisibilityOfViews() {
         if (mLayout == null) {
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 6aad4e2..8df287d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -69,7 +69,9 @@
         default void onSplitVisibilityChanged(boolean visible) {}
     }
 
-    /** Callback interface for listening to requests to enter split select */
+    /**
+     * Callback interface for listening to requests to enter split select. Used for desktop -> split
+     */
     interface SplitSelectListener {
         default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
                 int splitPosition, Rect taskBounds) {
@@ -90,6 +92,24 @@
     /** Unregisters listener that gets split screen callback. */
     void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
 
+    interface SplitInvocationListener {
+        /**
+         * Called whenever shell starts or stops the split screen animation
+         * @param animationRunning if {@code true} the animation has begun, if {@code false} the
+         *                         animation has finished
+         */
+        default void onSplitAnimationInvoked(boolean animationRunning) { }
+    }
+
+    /**
+     * Registers a {@link SplitInvocationListener} to notify when the animation to enter split
+     * screen has started and stopped
+     *
+     * @param executor callbacks to the listener will be executed on this executor
+     */
+    void registerSplitAnimationListener(@NonNull SplitInvocationListener listener,
+            @NonNull Executor executor);
+
     /** Called when device waking up finished. */
     void onFinishedWakingUp();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 547457b..b9d70e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -1166,6 +1166,12 @@
         }
 
         @Override
+        public void registerSplitAnimationListener(@NonNull SplitInvocationListener listener,
+                @NonNull Executor executor) {
+            mStageCoordinator.registerSplitAnimationListener(listener, executor);
+        }
+
+        @Override
         public void onFinishedWakingUp() {
             mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 1a53a1d..6e5b767 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -55,6 +55,7 @@
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 
 /** Manages transition animations for split-screen. */
 class SplitScreenTransitions {
@@ -79,6 +80,8 @@
 
     private Transitions.TransitionFinishCallback mFinishCallback = null;
     private SurfaceControl.Transaction mFinishTransaction;
+    private SplitScreen.SplitInvocationListener mSplitInvocationListener;
+    private Executor mSplitInvocationListenerExecutor;
 
     SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
             @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator) {
@@ -353,6 +356,10 @@
                     + " skip to start enter split transition since it already exist. ");
             return null;
         }
+        if (mSplitInvocationListenerExecutor != null && mSplitInvocationListener != null) {
+            mSplitInvocationListenerExecutor.execute(() -> mSplitInvocationListener
+                    .onSplitAnimationInvoked(true /*animationRunning*/));
+        }
         final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
         setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim);
         return transition;
@@ -457,6 +464,7 @@
 
             mPendingEnter.onConsumed(aborted);
             mPendingEnter = null;
+            mStageCoordinator.notifySplitAnimationFinished();
             ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for enter transition");
         } else if (isPendingDismiss(transition)) {
             mPendingDismiss.onConsumed(aborted);
@@ -529,6 +537,12 @@
         mTransitions.getAnimExecutor().execute(va::start);
     }
 
+    public void registerSplitAnimListener(@NonNull SplitScreen.SplitInvocationListener listener,
+            @NonNull Executor executor) {
+        mSplitInvocationListener = listener;
+        mSplitInvocationListenerExecutor = executor;
+    }
+
     /** Calls when the transition got consumed. */
     interface TransitionConsumedCallback {
         void onConsumed(boolean aborted);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 5e9451a..b10176d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -157,6 +157,7 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -237,6 +238,9 @@
     private DefaultMixedHandler mMixedHandler;
     private final Toast mSplitUnsupportedToast;
     private SplitRequest mSplitRequest;
+    /** Used to notify others of when shell is animating into split screen */
+    private SplitScreen.SplitInvocationListener mSplitInvocationListener;
+    private Executor mSplitInvocationListenerExecutor;
 
     /**
      * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support
@@ -247,6 +251,14 @@
         return false;
     }
 
+    /** NOTE: Will overwrite any previously set {@link #mSplitInvocationListener} */
+    public void registerSplitAnimationListener(
+            @NonNull SplitScreen.SplitInvocationListener listener, @NonNull Executor executor) {
+        mSplitInvocationListener = listener;
+        mSplitInvocationListenerExecutor = executor;
+        mSplitTransitions.registerSplitAnimListener(listener, executor);
+    }
+
     class SplitRequest {
         @SplitPosition
         int mActivatePosition;
@@ -535,7 +547,7 @@
                             null /* childrenToTop */, EXIT_REASON_UNKNOWN));
                     Log.w(TAG, splitFailureMessage("startShortcut",
                             "side stage was not populated"));
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                 }
 
                 if (finishedCallback != null) {
@@ -666,7 +678,7 @@
                             null /* childrenToTop */, EXIT_REASON_UNKNOWN));
                     Log.w(TAG, splitFailureMessage("startIntentLegacy",
                             "side stage was not populated"));
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                 }
 
                 if (apps != null) {
@@ -1287,7 +1299,7 @@
                             ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
             Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled",
                     "main or side stage was not populated."));
-            mSplitUnsupportedToast.show();
+            handleUnsupportedSplitStart();
         } else {
             mSyncQueue.queue(evictWct);
             mSyncQueue.runInSync(t -> {
@@ -1308,7 +1320,7 @@
                     ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
             Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished",
                     "main or side stage was not populated"));
-            mSplitUnsupportedToast.show();
+            handleUnsupportedSplitStart();
             return;
         }
 
@@ -2890,6 +2902,7 @@
             if (hasEnteringPip) {
                 mMixedHandler.animatePendingEnterPipFromSplit(transition, info,
                         startTransaction, finishTransaction, finishCallback);
+                notifySplitAnimationFinished();
                 return true;
             }
 
@@ -2924,6 +2937,7 @@
                 //                    the transition, or synchronize task-org callbacks.
             }
             // Use normal animations.
+            notifySplitAnimationFinished();
             return false;
         } else if (mMixedHandler != null && TransitionUtil.hasDisplayChange(info)) {
             // A display-change has been un-expectedly inserted into the transition. Redirect
@@ -2937,6 +2951,7 @@
                     mSplitLayout.update(startTransaction, true /* resetImePosition */);
                     startTransaction.apply();
                 }
+                notifySplitAnimationFinished();
                 return true;
             }
         }
@@ -3110,7 +3125,7 @@
                     pendingEnter.mRemoteHandler.onTransitionConsumed(transition,
                             false /*aborted*/, finishT);
                 }
-                mSplitUnsupportedToast.show();
+                handleUnsupportedSplitStart();
                 return true;
             }
         }
@@ -3139,6 +3154,7 @@
         final TransitionInfo.Change finalMainChild = mainChild;
         final TransitionInfo.Change finalSideChild = sideChild;
         enterTransition.setFinishedCallback((callbackWct, callbackT) -> {
+            notifySplitAnimationFinished();
             if (finalMainChild != null) {
                 if (!mainNotContainOpenTask) {
                     mMainStage.evictOtherChildren(callbackWct, finalMainChild.getTaskInfo().taskId);
@@ -3560,6 +3576,19 @@
                 mSplitLayout.isLeftRightSplit());
     }
 
+    private void handleUnsupportedSplitStart() {
+        mSplitUnsupportedToast.show();
+        notifySplitAnimationFinished();
+    }
+
+    void notifySplitAnimationFinished() {
+        if (mSplitInvocationListener == null || mSplitInvocationListenerExecutor == null) {
+            return;
+        }
+        mSplitInvocationListenerExecutor.execute(() ->
+                mSplitInvocationListener.onSplitAnimationInvoked(false /*animationRunning*/));
+    }
+
     /**
      * Logs the exit of splitscreen to a specific stage. This must be called before the exit is
      * executed.
@@ -3622,7 +3651,7 @@
                 if (!ENABLE_SHELL_TRANSITIONS) {
                     StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage,
                             EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                     return;
                 }
 
@@ -3642,7 +3671,7 @@
                         "app package " + taskInfo.baseActivity.getPackageName()
                         + " does not support splitscreen, or is a controlled activity type"));
                 if (splitScreenVisible) {
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                 }
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 9b2922d..4d3c763 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -61,6 +61,7 @@
 import android.view.WindowManager;
 import android.window.ITransitionPlayer;
 import android.window.RemoteTransition;
+import android.window.TaskFragmentOrganizer;
 import android.window.TransitionFilter;
 import android.window.TransitionInfo;
 import android.window.TransitionMetrics;
@@ -183,6 +184,13 @@
     /** Transition to resize PiP task. */
     public static final int TRANSIT_RESIZE_PIP = TRANSIT_FIRST_CUSTOM + 16;
 
+    /**
+     * The task fragment drag resize transition used by activity embedding.
+     */
+    public static final int TRANSIT_TASK_FRAGMENT_DRAG_RESIZE =
+            // TRANSIT_FIRST_CUSTOM + 17
+            TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_DRAG_RESIZE;
+
     private final ShellTaskOrganizer mOrganizer;
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index 0f24bb5..b8a19ad 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -13,3 +13,5 @@
 pbdr@google.com
 tkachenkoi@google.com
 mpodolian@google.com
+jeremysim@google.com
+peanutbutter@google.com
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 2ac72af..ea522cd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -20,6 +20,8 @@
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 
+import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -100,6 +102,20 @@
     }
 
     @Test
+    public void testTransitionTypeDragResize() {
+        final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TASK_FRAGMENT_DRAG_RESIZE, 0)
+                .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
+                .build();
+        final Animator animator = mAnimRunner.createAnimator(
+                info, mStartTransaction, mFinishTransaction,
+                () -> mFinishCallback.onTransitionFinished(null /* wct */),
+                new ArrayList());
+
+        // The animation should be empty when it is a jump cut for drag resize.
+        assertEquals(0, animator.getDuration());
+    }
+
+    @Test
     public void testInvalidCustomAnimation() {
         final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
                 .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 56d0f8e1..8de60b7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -115,27 +115,27 @@
 
     @Test
     public void testUpdateDivideBounds() {
-        mSplitLayout.updateDivideBounds(anyInt());
+        mSplitLayout.updateDividerBounds(anyInt());
         verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class), anyInt(),
                 anyInt());
     }
 
     @Test
     public void testSetDividePosition() {
-        mSplitLayout.setDividePosition(100, false /* applyLayoutChange */);
-        assertThat(mSplitLayout.getDividePosition()).isEqualTo(100);
+        mSplitLayout.setDividerPosition(100, false /* applyLayoutChange */);
+        assertThat(mSplitLayout.getDividerPosition()).isEqualTo(100);
         verify(mSplitLayoutHandler, never()).onLayoutSizeChanged(any(SplitLayout.class));
 
-        mSplitLayout.setDividePosition(200, true /* applyLayoutChange */);
-        assertThat(mSplitLayout.getDividePosition()).isEqualTo(200);
+        mSplitLayout.setDividerPosition(200, true /* applyLayoutChange */);
+        assertThat(mSplitLayout.getDividerPosition()).isEqualTo(200);
         verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class));
     }
 
     @Test
     public void testSetDivideRatio() {
-        mSplitLayout.setDividePosition(200, false /* applyLayoutChange */);
+        mSplitLayout.setDividerPosition(200, false /* applyLayoutChange */);
         mSplitLayout.setDivideRatio(SNAP_TO_50_50);
-        assertThat(mSplitLayout.getDividePosition()).isEqualTo(
+        assertThat(mSplitLayout.getDividerPosition()).isEqualTo(
                 mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position);
     }
 
@@ -152,7 +152,7 @@
         DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
                 SNAP_TO_START_AND_DISMISS);
 
-        mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
+        mSplitLayout.snapToTarget(mSplitLayout.getDividerPosition(), snapTarget);
         waitDividerFlingFinished();
         verify(mSplitLayoutHandler).onSnappedToDismiss(eq(false), anyInt());
     }
@@ -164,7 +164,7 @@
         DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
                 SNAP_TO_END_AND_DISMISS);
 
-        mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
+        mSplitLayout.snapToTarget(mSplitLayout.getDividerPosition(), snapTarget);
         waitDividerFlingFinished();
         verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true), anyInt());
     }
@@ -188,7 +188,7 @@
     }
 
     private void waitDividerFlingFinished() {
-        verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), anyInt(),
+        verify(mSplitLayout).flingDividerPosition(anyInt(), anyInt(), anyInt(),
                 mRunnableCaptor.capture());
         mRunnableCaptor.getValue().run();
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 5209d0e..41a81c1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -22,6 +22,7 @@
 import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
@@ -98,14 +99,28 @@
 
     private CompatUIWindowManager mWindowManager;
     private TaskInfo mTaskInfo;
+    private DisplayLayout mDisplayLayout;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
         mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = TASK_WIDTH;
+        displayInfo.logicalHeight = TASK_HEIGHT;
+        mDisplayLayout = new DisplayLayout(displayInfo,
+                mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false);
+        final InsetsState insetsState = new InsetsState();
+        insetsState.setDisplayFrame(new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT));
+        final InsetsSource insetsSource = new InsetsSource(
+                InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+        insetsSource.setFrame(0, TASK_HEIGHT - 200, TASK_WIDTH, TASK_HEIGHT);
+        insetsState.addSource(insetsSource);
+        mDisplayLayout.setInsets(mContext.getResources(), insetsState);
         mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
-                mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+                mCallback, mTaskListener, mDisplayLayout, new CompatUIHintsState(),
                 mCompatUIConfiguration, mOnRestartButtonClicked);
 
         spyOn(mWindowManager);
@@ -363,9 +378,9 @@
 
         // Update if the insets change on the existing display layout
         clearInvocations(mWindowManager);
-        InsetsState insetsState = new InsetsState();
+        final InsetsState insetsState = new InsetsState();
         insetsState.setDisplayFrame(new Rect(0, 0, 1000, 2000));
-        InsetsSource insetsSource = new InsetsSource(
+        final InsetsSource insetsSource = new InsetsSource(
                 InsetsSource.createId(null, 0, navigationBars()), navigationBars());
         insetsSource.setFrame(0, 1800, 1000, 2000);
         insetsState.addSource(insetsSource);
@@ -493,16 +508,14 @@
     @Test
     public void testShouldShowSizeCompatRestartButton() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON);
-
-        doReturn(86).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
+        doReturn(85).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
         mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
-                mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+                mCallback, mTaskListener, mDisplayLayout, new CompatUIHintsState(),
                 mCompatUIConfiguration, mOnRestartButtonClicked);
 
         // Simulate rotation of activity in square display
         TaskInfo taskInfo = createTaskInfo(true, CAMERA_COMPAT_CONTROL_HIDDEN);
-        taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
-        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 2000;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = TASK_HEIGHT;
         taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1850;
 
         assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
@@ -512,11 +525,21 @@
         assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
 
         // Simulate folding
-        taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 1000, 2000));
-        assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+        final InsetsState insetsState = new InsetsState();
+        insetsState.setDisplayFrame(new Rect(0, 0, 1000, TASK_HEIGHT));
+        final InsetsSource insetsSource = new InsetsSource(
+                InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+        insetsSource.setFrame(0, TASK_HEIGHT - 200, 1000, TASK_HEIGHT);
+        insetsState.addSource(insetsSource);
+        mDisplayLayout.setInsets(mContext.getResources(), insetsState);
+        mWindowManager.updateDisplayLayout(mDisplayLayout);
+        taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP - 100;
+        assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
 
-        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
-        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 500;
+        // Simulate floating app with 90& area, more than tolerance
+        taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 950;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1900;
         assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
     }
 
@@ -529,10 +552,10 @@
                 cameraCompatControlState;
         taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
         // Letterboxed activity that takes half the screen should show size compat restart button
-        taskInfo.configuration.windowConfiguration.setBounds(
-                new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT));
         taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
         taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+        // Screen width dp larger than a normal phone.
+        taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
         return taskInfo;
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index befc702..34b2eeb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -39,10 +39,13 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -63,6 +66,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
@@ -105,6 +109,8 @@
     @Mock private ShellExecutor mMainExecutor;
     @Mock private LaunchAdjacentController mLaunchAdjacentController;
     @Mock private DefaultMixedHandler mMixedHandler;
+    @Mock private SplitScreen.SplitInvocationListener mInvocationListener;
+    private final TestShellExecutor mTestShellExecutor = new TestShellExecutor();
     private SplitLayout mSplitLayout;
     private MainStage mMainStage;
     private SideStage mSideStage;
@@ -147,6 +153,7 @@
                 .setParentTaskId(mSideStage.mRootTaskInfo.taskId).build();
         doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager();
         doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager();
+        mStageCoordinator.registerSplitAnimationListener(mInvocationListener, mTestShellExecutor);
     }
 
     @Test
@@ -452,6 +459,15 @@
         mMainStage.activate(new WindowContainerTransaction(), true /* includingTopTask */);
     }
 
+    @Test
+    @UiThreadTest
+    public void testSplitInvocationCallback() {
+        enterSplit();
+        mTestShellExecutor.flushAll();
+        verify(mInvocationListener, times(1))
+                .onSplitAnimationInvoked(eq(true));
+    }
+
     private boolean containsSplitEnter(@NonNull WindowContainerTransaction wct) {
         for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
             WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i);
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index f9dc5fa..933a33e 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -272,7 +272,10 @@
     if (it == mLocked.spotControllers.end()) {
         mLocked.spotControllers.try_emplace(displayId, displayId, mContext);
     }
-    mLocked.spotControllers.at(displayId).setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits);
+    bool skipScreenshot = mLocked.displaysToSkipScreenshot.find(displayId) !=
+            mLocked.displaysToSkipScreenshot.end();
+    mLocked.spotControllers.at(displayId).setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits,
+                                                   skipScreenshot);
 }
 
 void PointerController::clearSpots() {
@@ -352,6 +355,17 @@
     mCursorController.setCustomPointerIcon(icon);
 }
 
+void PointerController::setSkipScreenshot(int32_t displayId, bool skip) {
+    if (!mEnabled) return;
+
+    std::scoped_lock lock(getLock());
+    if (skip) {
+        mLocked.displaysToSkipScreenshot.insert(displayId);
+    } else {
+        mLocked.displaysToSkipScreenshot.erase(displayId);
+    }
+}
+
 void PointerController::doInactivityTimeout() {
     fade(Transition::GRADUAL);
 }
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 6ee5707..d76ca5d 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -67,6 +67,7 @@
     void clearSpots() override;
     void updatePointerIcon(PointerIconStyle iconId) override;
     void setCustomPointerIcon(const SpriteIcon& icon) override;
+    void setSkipScreenshot(int32_t displayId, bool skip) override;
 
     virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout);
     void doInactivityTimeout();
@@ -115,6 +116,7 @@
 
         std::vector<gui::DisplayInfo> mDisplayInfos;
         std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
+        std::unordered_set<int32_t /* displayId */> displaysToSkipScreenshot;
     } mLocked GUARDED_BY(getLock());
 
     class DisplayInfoListener : public gui::WindowInfosListener {
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index a63453d..0baa929 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -129,7 +129,7 @@
             update.state.surfaceVisible = false;
             update.state.surfaceControl =
                     obtainSurface(update.state.surfaceWidth, update.state.surfaceHeight,
-                                  update.state.displayId);
+                                  update.state.displayId, update.state.skipScreenshot);
             if (update.state.surfaceControl != NULL) {
                 update.surfaceChanged = surfaceChanged = true;
             }
@@ -209,7 +209,7 @@
               (update.state.dirty &
                (DIRTY_ALPHA | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER |
                 DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID | DIRTY_ICON_STYLE |
-                DIRTY_DRAW_DROP_SHADOW))))) {
+                DIRTY_DRAW_DROP_SHADOW | DIRTY_SKIP_SCREENSHOT))))) {
             needApplyTransaction = true;
 
             if (wantSurfaceVisibleAndDrawn
@@ -260,6 +260,14 @@
                 t.setLayer(update.state.surfaceControl, surfaceLayer);
             }
 
+            if (wantSurfaceVisibleAndDrawn &&
+                (becomingVisible || (update.state.dirty & DIRTY_SKIP_SCREENSHOT))) {
+                int32_t flags =
+                        update.state.skipScreenshot ? ISurfaceComposerClient::eSkipScreenshot : 0;
+                t.setFlags(update.state.surfaceControl, flags,
+                           ISurfaceComposerClient::eSkipScreenshot);
+            }
+
             if (becomingVisible) {
                 t.show(update.state.surfaceControl);
 
@@ -332,8 +340,8 @@
     }
 }
 
-sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height,
-                                                   int32_t displayId) {
+sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height, int32_t displayId,
+                                                   bool hideOnMirrored) {
     ensureSurfaceComposerClient();
 
     const sp<SurfaceControl> parent = mParentSurfaceProvider(displayId);
@@ -341,11 +349,13 @@
         ALOGE("Failed to get the parent surface for pointers on display %d", displayId);
     }
 
+    int32_t createFlags = ISurfaceComposerClient::eHidden | ISurfaceComposerClient::eCursorWindow;
+    if (hideOnMirrored) {
+        createFlags |= ISurfaceComposerClient::eSkipScreenshot;
+    }
     const sp<SurfaceControl> surfaceControl =
             mSurfaceComposerClient->createSurface(String8("Sprite"), width, height,
-                                                  PIXEL_FORMAT_RGBA_8888,
-                                                  ISurfaceComposerClient::eHidden |
-                                                          ISurfaceComposerClient::eCursorWindow,
+                                                  PIXEL_FORMAT_RGBA_8888, createFlags,
                                                   parent ? parent->getHandle() : nullptr);
     if (surfaceControl == nullptr || !surfaceControl->isValid()) {
         ALOGE("Error creating sprite surface.");
@@ -474,6 +484,15 @@
     }
 }
 
+void SpriteController::SpriteImpl::setSkipScreenshot(bool skip) {
+    AutoMutex _l(mController.mLock);
+
+    if (mLocked.state.skipScreenshot != skip) {
+        mLocked.state.skipScreenshot = skip;
+        invalidateLocked(DIRTY_SKIP_SCREENSHOT);
+    }
+}
+
 void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
     bool wasDirty = mLocked.state.dirty;
     mLocked.state.dirty |= dirty;
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 35776e9..4e4ba65 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -96,6 +96,10 @@
 
     /* Sets the id of the display where the sprite should be shown. */
     virtual void setDisplayId(int32_t displayId) = 0;
+
+    /* Sets the flag to hide sprite on mirrored displays.
+     * This will add ISurfaceComposerClient::eSkipScreenshot flag to the sprite. */
+    virtual void setSkipScreenshot(bool skip) = 0;
 };
 
 /*
@@ -152,6 +156,7 @@
         DIRTY_DISPLAY_ID = 1 << 7,
         DIRTY_ICON_STYLE = 1 << 8,
         DIRTY_DRAW_DROP_SHADOW = 1 << 9,
+        DIRTY_SKIP_SCREENSHOT = 1 << 10,
     };
 
     /* Describes the state of a sprite.
@@ -182,6 +187,7 @@
         int32_t surfaceHeight;
         bool surfaceDrawn;
         bool surfaceVisible;
+        bool skipScreenshot;
 
         inline bool wantSurfaceVisible() const {
             return visible && alpha > 0.0f && icon.isValid();
@@ -209,6 +215,7 @@
         virtual void setAlpha(float alpha);
         virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
         virtual void setDisplayId(int32_t displayId);
+        virtual void setSkipScreenshot(bool skip);
 
         inline const SpriteState& getStateLocked() const {
             return mLocked.state;
@@ -272,7 +279,8 @@
     void doDisposeSurfaces();
 
     void ensureSurfaceComposerClient();
-    sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, int32_t displayId);
+    sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, int32_t displayId,
+                                     bool hideOnMirrored);
 };
 
 } // namespace android
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index 99952aa..530d541 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -40,12 +40,13 @@
 // --- Spot ---
 
 void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float newX, float newY,
-                                             int32_t displayId) {
+                                             int32_t displayId, bool skipScreenshot) {
     sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
     sprite->setAlpha(alpha);
     sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
     sprite->setPosition(newX, newY);
     sprite->setDisplayId(displayId);
+    sprite->setSkipScreenshot(skipScreenshot);
     x = newX;
     y = newY;
 
@@ -84,7 +85,7 @@
 }
 
 void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                                   BitSet32 spotIdBits) {
+                                   BitSet32 spotIdBits, bool skipScreenshot) {
 #if DEBUG_SPOT_UPDATES
     ALOGD("setSpots: idBits=%08x", spotIdBits.value);
     for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
@@ -116,7 +117,7 @@
             spot = createAndAddSpotLocked(id, mLocked.displaySpots);
         }
 
-        spot->updateSprite(&icon, x, y, mDisplayId);
+        spot->updateSprite(&icon, x, y, mDisplayId, skipScreenshot);
     }
 
     for (Spot* spot : mLocked.displaySpots) {
diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h
index 5bbc75d..608653c 100644
--- a/libs/input/TouchSpotController.h
+++ b/libs/input/TouchSpotController.h
@@ -32,7 +32,7 @@
     TouchSpotController(int32_t displayId, PointerControllerContext& context);
     ~TouchSpotController();
     void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                  BitSet32 spotIdBits);
+                  BitSet32 spotIdBits, bool skipScreenshot);
     void clearSpots();
 
     void reloadSpotResources();
@@ -59,7 +59,8 @@
                 y(0.0f),
                 mLastIcon(nullptr) {}
 
-        void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId);
+        void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId,
+                          bool skipScreenshot);
         void dump(std::string& out, const char* prefix = "") const;
 
     private:
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index a1bb5b3..fcf226c 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -372,6 +372,45 @@
             << "The pointer display changes to invalid when PointerController is destroyed.";
 }
 
+TEST_F(PointerControllerTest, updatesSkipScreenshotFlagForTouchSpots) {
+    ensureDisplayViewportIsSet();
+
+    PointerCoords testSpotCoords;
+    testSpotCoords.clear();
+    testSpotCoords.setAxisValue(AMOTION_EVENT_AXIS_X, 1);
+    testSpotCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, 1);
+    BitSet32 testIdBits;
+    testIdBits.markBit(0);
+    std::array<uint32_t, MAX_POINTER_ID + 1> testIdToIndex;
+
+    sp<MockSprite> testSpotSprite(new NiceMock<MockSprite>);
+
+    // By default sprite is not marked secure
+    EXPECT_CALL(*mSpriteController, createSprite).WillOnce(Return(testSpotSprite));
+    EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false));
+
+    // Update spots to sync state with sprite
+    mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
+                                 ADISPLAY_ID_DEFAULT);
+    testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
+
+    // Marking the display to skip screenshot should update sprite as well
+    mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, true);
+    EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true));
+
+    // Update spots to sync state with sprite
+    mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
+                                 ADISPLAY_ID_DEFAULT);
+    testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
+
+    // Reset flag and verify again
+    mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, false);
+    EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false));
+    mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
+                                 ADISPLAY_ID_DEFAULT);
+    testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
+}
+
 class PointerControllerWindowInfoListenerTest : public Test {};
 
 TEST_F(PointerControllerWindowInfoListenerTest,
diff --git a/libs/input/tests/mocks/MockSprite.h b/libs/input/tests/mocks/MockSprite.h
index 013b79c..0867221 100644
--- a/libs/input/tests/mocks/MockSprite.h
+++ b/libs/input/tests/mocks/MockSprite.h
@@ -34,6 +34,7 @@
     MOCK_METHOD(void, setAlpha, (float), (override));
     MOCK_METHOD(void, setTransformationMatrix, (const SpriteTransformationMatrix&), (override));
     MOCK_METHOD(void, setDisplayId, (int32_t), (override));
+    MOCK_METHOD(void, setSkipScreenshot, (bool), (override));
 };
 
 }  // namespace android
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 554fe5e..b2838c8 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -2064,24 +2064,31 @@
         }
 
         /**
-         * Transfers to a given route for the remote session. The given route must be included in
-         * {@link RoutingSessionInfo#getTransferableRoutes()}.
+         * Attempts a transfer to a {@link RoutingSessionInfo#getTransferableRoutes() transferable
+         * route}.
          *
+         * <p>Transferring to a transferable route does not require the app to transfer the playback
+         * state from one route to the other. The route provider completely manages the transfer. An
+         * example of provider-managed transfers are the switches between the system's routes, like
+         * the built-in speakers and a BT headset.
+         *
+         * @return True if the transfer is handled by this controller, or false if a new controller
+         *     should be created instead.
          * @see RoutingSessionInfo#getSelectedRoutes()
          * @see RoutingSessionInfo#getTransferableRoutes()
          * @see ControllerCallback#onControllerUpdated
          */
-        void transferToRoute(@NonNull MediaRoute2Info route) {
+        boolean tryTransferWithinProvider(@NonNull MediaRoute2Info route) {
             Objects.requireNonNull(route, "route must not be null");
             synchronized (mControllerLock) {
                 if (isReleased()) {
                     Log.w(TAG, "transferToRoute: Called on released controller. Ignoring.");
-                    return;
+                    return true;
                 }
 
                 if (!mSessionInfo.getTransferableRoutes().contains(route.getId())) {
                     Log.w(TAG, "Ignoring transferring to a non-transferable route=" + route);
-                    return;
+                    return false;
                 }
             }
 
@@ -2096,6 +2103,7 @@
                     Log.e(TAG, "Unable to transfer to route for session.", ex);
                 }
             }
+            return true;
         }
 
         /**
@@ -3587,20 +3595,14 @@
             }
 
             RoutingController controller = getCurrentController();
-            if (controller
-                    .getRoutingSessionInfo()
-                    .getTransferableRoutes()
-                    .contains(route.getId())) {
-                controller.transferToRoute(route);
-                return;
+            if (!controller.tryTransferWithinProvider(route)) {
+                requestCreateController(
+                        controller,
+                        route,
+                        MANAGER_REQUEST_ID_NONE,
+                        Process.myUserHandle(),
+                        mContext.getPackageName());
             }
-
-            requestCreateController(
-                    controller,
-                    route,
-                    MANAGER_REQUEST_ID_NONE,
-                    Process.myUserHandle(),
-                    mContext.getPackageName());
         }
 
         @Override
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 46a5138..0bae63a 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -24,7 +24,7 @@
   <string name="string_cancel">Cancel</string>
   <!-- This is a label for a button that takes user to the next screen. [CHAR LIMIT=20] -->
   <string name="string_continue">Continue</string>
-  <!-- This is a label for a button that leads to a holistic view of all different options where the user can save their new app credential. [CHAR LIMIT=20] -->
+  <!-- This is a label for a button that leads to a holistic view of all different options where the user can save their new app credential. [CHAR LIMIT=30] -->
   <string name="string_more_options">Save another way</string>
   <!-- This is a label for a button that links to additional information about passkeys. [CHAR LIMIT=20] -->
   <string name="string_learn_more">Learn more</string>
@@ -174,4 +174,4 @@
   <!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] -->
   <string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string>
   <string name="more_options_content_description">More</string>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index a7b7da5..30bec77 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -649,6 +649,9 @@
         for (CachedBluetoothDevice cbd : mMemberDevices) {
             cbd.setName(name);
         }
+        if (mSubDevice != null) {
+            mSubDevice.setName(name);
+        }
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index 9bf42f9..ed964a9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -100,6 +100,7 @@
             // device.
             if (hearingAidDevice != null) {
                 hearingAidDevice.setSubDevice(newDevice);
+                newDevice.setName(hearingAidDevice.getName());
                 return true;
             }
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
index 8e3df8b..2de2174 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
@@ -48,17 +48,15 @@
     private static final String BT_HEARING_AIDS_PAIRED_HISTORY = "bt_hearing_aids_paired_history";
     private static final String BT_HEARING_AIDS_CONNECTED_HISTORY =
             "bt_hearing_aids_connected_history";
-    private static final String BT_HEARING_DEVICES_PAIRED_HISTORY =
+    private static final String BT_HEARABLE_DEVICES_PAIRED_HISTORY =
             "bt_hearing_devices_paired_history";
-    private static final String BT_HEARING_DEVICES_CONNECTED_HISTORY =
+    private static final String BT_HEARABLE_DEVICES_CONNECTED_HISTORY =
             "bt_hearing_devices_connected_history";
-    private static final String BT_HEARING_USER_CATEGORY = "bt_hearing_user_category";
-
     private static final String HISTORY_RECORD_DELIMITER = ",";
     static final String CATEGORY_HEARING_AIDS = "A11yHearingAidsUser";
     static final String CATEGORY_NEW_HEARING_AIDS = "A11yNewHearingAidsUser";
-    static final String CATEGORY_HEARING_DEVICES = "A11yHearingDevicesUser";
-    static final String CATEGORY_NEW_HEARING_DEVICES = "A11yNewHearingDevicesUser";
+    static final String CATEGORY_HEARABLE_DEVICES = "A11yHearingDevicesUser";
+    static final String CATEGORY_NEW_HEARABLE_DEVICES = "A11yNewHearingDevicesUser";
 
     static final int PAIRED_HISTORY_EXPIRED_DAY = 30;
     static final int CONNECTED_HISTORY_EXPIRED_DAY = 7;
@@ -73,14 +71,14 @@
             HistoryType.TYPE_UNKNOWN,
             HistoryType.TYPE_HEARING_AIDS_PAIRED,
             HistoryType.TYPE_HEARING_AIDS_CONNECTED,
-            HistoryType.TYPE_HEARING_DEVICES_PAIRED,
-            HistoryType.TYPE_HEARING_DEVICES_CONNECTED})
+            HistoryType.TYPE_HEARABLE_DEVICES_PAIRED,
+            HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED})
     public @interface HistoryType {
         int TYPE_UNKNOWN = -1;
         int TYPE_HEARING_AIDS_PAIRED = 0;
         int TYPE_HEARING_AIDS_CONNECTED = 1;
-        int TYPE_HEARING_DEVICES_PAIRED = 2;
-        int TYPE_HEARING_DEVICES_CONNECTED = 3;
+        int TYPE_HEARABLE_DEVICES_PAIRED = 2;
+        int TYPE_HEARABLE_DEVICES_CONNECTED = 3;
     }
 
     private static final HashMap<String, Integer> sDeviceAddressToBondEntryMap = new HashMap<>();
@@ -127,8 +125,8 @@
     }
 
     /**
-     * Updates corresponding history if we found the device is a hearing device after profile state
-     * changed.
+     * Updates corresponding history if we found the device is a hearing related device after
+     * profile state changed.
      *
      * @param context the request context
      * @param cachedDevice the remote device
@@ -148,7 +146,7 @@
             } else if (cachedDevice.getProfiles().stream().anyMatch(
                     p -> (p instanceof A2dpSinkProfile || p instanceof HeadsetProfile))) {
                 HearingAidStatsLogUtils.addCurrentTimeToHistory(context,
-                        HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_PAIRED);
+                        HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_PAIRED);
             }
             removeFromJustBonded(cachedDevice.getAddress());
         }
@@ -161,7 +159,7 @@
                         HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_CONNECTED);
             } else if (profile instanceof A2dpSinkProfile || profile instanceof HeadsetProfile) {
                 HearingAidStatsLogUtils.addCurrentTimeToHistory(context,
-                        HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED);
+                        HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED);
             }
         }
     }
@@ -169,18 +167,13 @@
     /**
      * Returns the user category if the user is already categorized. Otherwise, checks the
      * history and sees if the user is categorized as one of {@link #CATEGORY_HEARING_AIDS},
-     * {@link #CATEGORY_NEW_HEARING_AIDS}, {@link #CATEGORY_HEARING_DEVICES}, and
-     * {@link #CATEGORY_NEW_HEARING_DEVICES}.
+     * {@link #CATEGORY_NEW_HEARING_AIDS}, {@link #CATEGORY_HEARABLE_DEVICES}, and
+     * {@link #CATEGORY_NEW_HEARABLE_DEVICES}.
      *
      * @param context the request context
      * @return the category which user belongs to
      */
     public static synchronized String getUserCategory(Context context) {
-        String userCategory = getSharedPreferences(context).getString(BT_HEARING_USER_CATEGORY, "");
-        if (!userCategory.isEmpty()) {
-            return userCategory;
-        }
-
         LinkedList<Long> hearingAidsConnectedHistory = getHistory(context,
                 HistoryType.TYPE_HEARING_AIDS_CONNECTED);
         if (hearingAidsConnectedHistory != null
@@ -192,29 +185,29 @@
             // will be categorized as CATEGORY_HEARING_AIDS.
             if (hearingAidsPairedHistory != null
                     && hearingAidsPairedHistory.size() >= VALID_PAIRED_EVENT_COUNT) {
-                userCategory = CATEGORY_NEW_HEARING_AIDS;
+                return CATEGORY_NEW_HEARING_AIDS;
             } else {
-                userCategory = CATEGORY_HEARING_AIDS;
+                return CATEGORY_HEARING_AIDS;
             }
         }
 
-        LinkedList<Long> hearingDevicesConnectedHistory = getHistory(context,
-                HistoryType.TYPE_HEARING_DEVICES_CONNECTED);
-        if (hearingDevicesConnectedHistory != null
-                && hearingDevicesConnectedHistory.size() >= VALID_CONNECTED_EVENT_COUNT) {
-            LinkedList<Long> hearingDevicesPairedHistory = getHistory(context,
-                    HistoryType.TYPE_HEARING_DEVICES_PAIRED);
+        LinkedList<Long> hearableDevicesConnectedHistory = getHistory(context,
+                HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED);
+        if (hearableDevicesConnectedHistory != null
+                && hearableDevicesConnectedHistory.size() >= VALID_CONNECTED_EVENT_COUNT) {
+            LinkedList<Long> hearableDevicesPairedHistory = getHistory(context,
+                    HistoryType.TYPE_HEARABLE_DEVICES_PAIRED);
             // Since paired history will be cleared after 30 days. If there's any record within 30
-            // days, the user will be categorized as CATEGORY_NEW_HEARING_DEVICES. Otherwise, the
-            // user will be categorized as CATEGORY_HEARING_DEVICES.
-            if (hearingDevicesPairedHistory != null
-                    && hearingDevicesPairedHistory.size() >= VALID_PAIRED_EVENT_COUNT) {
-                userCategory = CATEGORY_NEW_HEARING_DEVICES;
+            // days, the user will be categorized as CATEGORY_NEW_HEARABLE_DEVICES. Otherwise, the
+            // user will be categorized as CATEGORY_HEARABLE_DEVICES.
+            if (hearableDevicesPairedHistory != null
+                    && hearableDevicesPairedHistory.size() >= VALID_PAIRED_EVENT_COUNT) {
+                return CATEGORY_NEW_HEARABLE_DEVICES;
             } else {
-                userCategory = CATEGORY_HEARING_DEVICES;
+                return CATEGORY_HEARABLE_DEVICES;
             }
         }
-        return userCategory;
+        return "";
     }
 
     /**
@@ -245,7 +238,7 @@
     }
 
     /**
-     * Adds current timestamp into BT hearing devices related history.
+     * Adds current timestamp into BT hearing related devices history.
      * @param context the request context
      * @param type the type of history to store the data. See {@link HistoryType}.
      */
@@ -279,13 +272,13 @@
     static synchronized LinkedList<Long> getHistory(Context context, @HistoryType int type) {
         String spName = HISTORY_TYPE_TO_SP_NAME_MAPPING.get(type);
         if (BT_HEARING_AIDS_PAIRED_HISTORY.equals(spName)
-                || BT_HEARING_DEVICES_PAIRED_HISTORY.equals(spName)) {
+                || BT_HEARABLE_DEVICES_PAIRED_HISTORY.equals(spName)) {
             LinkedList<Long> history = convertToHistoryList(
                     getSharedPreferences(context).getString(spName, ""));
             removeRecordsBeforeDay(history, PAIRED_HISTORY_EXPIRED_DAY);
             return history;
         } else if (BT_HEARING_AIDS_CONNECTED_HISTORY.equals(spName)
-                || BT_HEARING_DEVICES_CONNECTED_HISTORY.equals(spName)) {
+                || BT_HEARABLE_DEVICES_CONNECTED_HISTORY.equals(spName)) {
             LinkedList<Long> history = convertToHistoryList(
                     getSharedPreferences(context).getString(spName, ""));
             removeRecordsBeforeDay(history, CONNECTED_HISTORY_EXPIRED_DAY);
@@ -352,9 +345,9 @@
         HISTORY_TYPE_TO_SP_NAME_MAPPING.put(
                 HistoryType.TYPE_HEARING_AIDS_CONNECTED, BT_HEARING_AIDS_CONNECTED_HISTORY);
         HISTORY_TYPE_TO_SP_NAME_MAPPING.put(
-                HistoryType.TYPE_HEARING_DEVICES_PAIRED, BT_HEARING_DEVICES_PAIRED_HISTORY);
+                HistoryType.TYPE_HEARABLE_DEVICES_PAIRED, BT_HEARABLE_DEVICES_PAIRED_HISTORY);
         HISTORY_TYPE_TO_SP_NAME_MAPPING.put(
-                HistoryType.TYPE_HEARING_DEVICES_CONNECTED, BT_HEARING_DEVICES_CONNECTED_HISTORY);
+                HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED, BT_HEARABLE_DEVICES_CONNECTED_HISTORY);
     }
     private HearingAidStatsLogUtils() {}
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index b356f54..b4bd482 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1703,6 +1703,30 @@
     }
 
     @Test
+    public void setName_memberDeviceNameIsSet() {
+        when(mDevice.getAlias()).thenReturn(DEVICE_NAME);
+        when(mSubDevice.getAlias()).thenReturn(DEVICE_NAME);
+
+        mCachedDevice.addMemberDevice(mSubCachedDevice);
+        mCachedDevice.setName(DEVICE_ALIAS);
+
+        verify(mDevice).setAlias(DEVICE_ALIAS);
+        verify(mSubDevice).setAlias(DEVICE_ALIAS);
+    }
+
+    @Test
+    public void setName_subDeviceNameIsSet() {
+        when(mDevice.getAlias()).thenReturn(DEVICE_NAME);
+        when(mSubDevice.getAlias()).thenReturn(DEVICE_NAME);
+
+        mCachedDevice.setSubDevice(mSubCachedDevice);
+        mCachedDevice.setName(DEVICE_ALIAS);
+
+        verify(mDevice).setAlias(DEVICE_ALIAS);
+        verify(mSubDevice).setAlias(DEVICE_ALIAS);
+    }
+
+    @Test
     public void getProfileConnectionState_nullProfile_returnDisconnected() {
         assertThat(mCachedDevice.getProfileConnectionState(null)).isEqualTo(
                 BluetoothProfile.STATE_DISCONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 1f0da90..4188d2e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -299,6 +299,7 @@
         mHearingAidDeviceManager.setSubDeviceIfNeeded(mCachedDevice2);
 
         assertThat(mCachedDevice1.getSubDevice()).isEqualTo(mCachedDevice2);
+        verify(mDevice2).setAlias(DEVICE_ALIAS_1);
     }
 
     /**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java
index bd5a022..cd16721 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java
@@ -145,20 +145,29 @@
     }
 
     @Test
-    public void getUserCategory_hearingDevicesUser() {
-        prepareHearingDevicesUserHistory();
+    public void getUserCategory_hearableDevicesUser() {
+        prepareHearableDevicesUserHistory();
 
         assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
-                HearingAidStatsLogUtils.CATEGORY_HEARING_DEVICES);
+                HearingAidStatsLogUtils.CATEGORY_HEARABLE_DEVICES);
     }
 
     @Test
-    public void getUserCategory_newHearingDevicesUser() {
-        prepareHearingDevicesUserHistory();
+    public void getUserCategory_newHearableDevicesUser() {
+        prepareHearableDevicesUserHistory();
         prepareNewUserHistory();
 
         assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
-                HearingAidStatsLogUtils.CATEGORY_NEW_HEARING_DEVICES);
+                HearingAidStatsLogUtils.CATEGORY_NEW_HEARABLE_DEVICES);
+    }
+
+    @Test
+    public void getUserCategory_bothHearingAidsAndHearableDevicesUser_returnHearingAidsUser() {
+        prepareHearingAidsUserHistory();
+        prepareHearableDevicesUserHistory();
+
+        assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
+                HearingAidStatsLogUtils.CATEGORY_HEARING_AIDS);
     }
 
     private long convertToStartOfDayTime(long timestamp) {
@@ -176,12 +185,12 @@
         }
     }
 
-    private void prepareHearingDevicesUserHistory() {
+    private void prepareHearableDevicesUserHistory() {
         final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
         for (int i = CONNECTED_HISTORY_EXPIRED_DAY - 1; i >= 0; i--) {
             final long data = todayStartOfDay - TimeUnit.DAYS.toMillis(i);
             HearingAidStatsLogUtils.addToHistory(mContext,
-                    HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED, data);
+                    HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED, data);
         }
     }
 
@@ -191,6 +200,6 @@
         HearingAidStatsLogUtils.addToHistory(mContext,
                 HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_PAIRED, data);
         HearingAidStatsLogUtils.addToHistory(mContext,
-                HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_PAIRED, data);
+                HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_PAIRED, data);
     }
 }
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index ed62ce7..65c5708 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -362,7 +362,6 @@
         "device_state_flags_lib",
         "kotlinx_coroutines_android",
         "kotlinx_coroutines",
-        "kotlinx_coroutines_guava",
         "//frameworks/libs/systemui:iconloader_base",
         "SystemUI-tags",
         "SystemUI-proto",
@@ -383,7 +382,6 @@
         "androidx.compose.material_material-icons-extended",
         "androidx.activity_activity-compose",
         "androidx.compose.animation_animation-graphics",
-        "device_policy_aconfig_flags_lib",
     ],
     libs: [
         "keepanno-annotations",
@@ -543,7 +541,6 @@
         "androidx.activity_activity-compose",
         "androidx.compose.animation_animation-graphics",
         "TraceurCommon",
-        "kotlinx_coroutines_guava",
     ],
 }
 
@@ -625,7 +622,6 @@
         "//frameworks/libs/systemui:compilelib",
         "SystemUI-tests-base",
         "androidx.compose.runtime_runtime",
-        "SystemUI-core",
     ],
     libs: [
         "keepanno-annotations",
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 0c89a5d..deab818 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -59,13 +59,16 @@
       ]
     }
   ],
-  
+
   "auto-end-to-end-postsubmit": [
     {
       "name": "AndroidAutomotiveHomeTests",
       "options" : [
         {
           "include-filter": "android.platform.tests.HomeTest"
+        },
+        {
+          "exclude-filter": "android.platform.tests.HomeTest#testAssistantWidget"
         }
       ]
     },
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index fd90bd9..de090f4 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -581,6 +581,16 @@
 }
 
 flag {
+   name: "contextual_tips_assistant_dismiss_fix"
+   namespace: "systemui"
+   description: "Improve assistant dismiss signal accuracy for contextual tips."
+   bug: "334759504"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
    name: "shaderlib_loading_effect_refactor"
    namespace: "systemui"
    description: "Extend shader library to provide the common loading effects."
@@ -796,7 +806,7 @@
     name: "dream_input_session_pilfer_once"
     namespace: "systemui"
     description: "Pilfer at most once per input session"
-    bug: "324600132"
+    bug: "333596426"
     metadata {
       purpose: PURPOSE_BUGFIX
     }
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index addcaf4..04ac748 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -38,8 +38,9 @@
     defaults: ["AndroidLintCheckerTestDefaults"],
     srcs: ["tests/**/*.kt"],
     data: [
-        ":framework",
         ":androidx.annotation_annotation",
+        ":dagger2",
+        ":framework",
         ":kotlinx-coroutines-core",
     ],
     static_libs: [
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SingletonAndroidComponentDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SingletonAndroidComponentDetector.kt
new file mode 100644
index 0000000..68ec1ee
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SingletonAndroidComponentDetector.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.AnnotationInfo
+import com.android.tools.lint.detector.api.AnnotationUsageInfo
+import com.android.tools.lint.detector.api.AnnotationUsageType
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UClass
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+
+/**
+ * Prevents binding Activities, Services, and BroadcastReceivers as Singletons in the Dagger graph.
+ *
+ * It is OK to mark a BroadcastReceiver as singleton as long as it is being constructed/injected and
+ * registered directly in the code. If instead it is declared in the manifest, and we let Android
+ * construct it for us, we also need to let Android destroy it for us, so don't allow marking it as
+ * singleton.
+ */
+class SingletonAndroidComponentDetector : Detector(), SourceCodeScanner {
+    override fun applicableAnnotations(): List<String> {
+        return listOf(
+            "com.android.systemui.dagger.SysUISingleton",
+        )
+    }
+
+    override fun isApplicableAnnotationUsage(type: AnnotationUsageType): Boolean =
+        type == AnnotationUsageType.DEFINITION
+
+    override fun visitAnnotationUsage(
+        context: JavaContext,
+        element: UElement,
+        annotationInfo: AnnotationInfo,
+        usageInfo: AnnotationUsageInfo
+    ) {
+        if (element !is UAnnotation) {
+            return
+        }
+
+        val parent = element.uastParent ?: return
+
+        if (isInvalidBindingMethod(parent)) {
+            context.report(
+                ISSUE,
+                element,
+                context.getLocation(element),
+                "Do not bind Activities, Services, or BroadcastReceivers as Singleton."
+            )
+        } else if (isInvalidClassDeclaration(parent)) {
+            context.report(
+                ISSUE,
+                element,
+                context.getLocation(element),
+                "Do not mark Activities or Services as Singleton."
+            )
+        }
+    }
+
+    private fun isInvalidBindingMethod(parent: UElement): Boolean {
+        if (parent !is UMethod) {
+            return false
+        }
+
+        if (
+            parent.returnType?.canonicalText !in
+                listOf(
+                    "android.app.Activity",
+                    "android.app.Service",
+                    "android.content.BroadcastReceiver",
+                )
+        ) {
+            return false
+        }
+
+        if (
+            !MULTIBIND_ANNOTATIONS.all { it in parent.annotations.map { it.qualifiedName } } &&
+                !MULTIPROVIDE_ANNOTATIONS.all { it in parent.annotations.map { it.qualifiedName } }
+        ) {
+            return false
+        }
+        return true
+    }
+
+    private fun isInvalidClassDeclaration(parent: UElement): Boolean {
+        if (parent !is UClass) {
+            return false
+        }
+
+        if (
+            parent.javaPsi.superClass?.qualifiedName !in
+                listOf(
+                    "android.app.Activity",
+                    "android.app.Service",
+                    // Fine to mark BroadcastReceiver as singleton in this scenario
+                )
+        ) {
+            return false
+        }
+
+        return true
+    }
+
+    companion object {
+        @JvmField
+        val ISSUE: Issue =
+            Issue.create(
+                id = "SingletonAndroidComponent",
+                briefDescription = "Activity, Service, or BroadcastReceiver marked as Singleton",
+                explanation =
+                    """Activities, Services, and BroadcastReceivers are created and destroyed by
+                        the Android System Server. Marking them with a Dagger scope
+                        results in them being cached and reused by Dagger. Trying to reuse a
+                        component like this will make for a very bad time.""",
+                category = Category.CORRECTNESS,
+                priority = 10,
+                severity = Severity.ERROR,
+                moreInfo =
+                    "https://developer.android.com/guide/components/activities/process-lifecycle",
+                // Note that JAVA_FILE_SCOPE also includes Kotlin source files.
+                implementation =
+                    Implementation(
+                        SingletonAndroidComponentDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                    )
+            )
+
+        private val MULTIBIND_ANNOTATIONS =
+            listOf("dagger.Binds", "dagger.multibindings.IntoMap", "dagger.multibindings.ClassKey")
+
+        val MULTIPROVIDE_ANNOTATIONS =
+            listOf(
+                "dagger.Provides",
+                "dagger.multibindings.IntoMap",
+                "dagger.multibindings.ClassKey"
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index e93264c..cecbc47 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -40,6 +40,7 @@
                 RegisterReceiverViaContextDetector.ISSUE,
                 SoftwareBitmapDetector.ISSUE,
                 NonInjectedServiceDetector.ISSUE,
+                SingletonAndroidComponentDetector.ISSUE,
                 StaticSettingsProviderDetector.ISSUE,
                 DemotingTestWithoutBugDetector.ISSUE,
                 TestFunctionNameViolationDetector.ISSUE,
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
index e1cca88..8396f3f 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -21,8 +21,9 @@
 
 internal val libraryNames =
     arrayOf(
-        "framework.jar",
         "androidx.annotation_annotation.jar",
+        "dagger2.jar",
+        "framework.jar",
         "kotlinx-coroutines-core.jar",
     )
 
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SingletonAndroidComponentDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SingletonAndroidComponentDetectorTest.kt
new file mode 100644
index 0000000..0606af8
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SingletonAndroidComponentDetectorTest.kt
@@ -0,0 +1,182 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class SingletonAndroidComponentDetectorTest : SystemUILintDetectorTest() {
+    override fun getDetector(): Detector = SingletonAndroidComponentDetector()
+
+    override fun getIssues(): List<Issue> = listOf(SingletonAndroidComponentDetector.ISSUE)
+
+    @Test
+    fun testBindsServiceAsSingleton() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                    package test.pkg
+
+                    import android.app.Service
+                    import com.android.systemui.dagger.SysUISingleton
+                    import dagger.Binds
+                    import dagger.Module
+                    import dagger.multibindings.ClassKey
+                    import dagger.multibindings.IntoMap
+
+                    @Module
+                    interface BadModule {
+                       @SysUISingleton
+                       @Binds
+                       @IntoMap
+                       @ClassKey(SingletonService::class)
+                       fun bindSingletonService(service: SingletonService): Service
+                    }
+                """
+                        .trimIndent()
+                ),
+                *stubs
+            )
+            .issues(SingletonAndroidComponentDetector.ISSUE)
+            .run()
+            .expect(
+                """
+                src/test/pkg/BadModule.kt:12: Error: Do not bind Activities, Services, or BroadcastReceivers as Singleton. [SingletonAndroidComponent]
+                   @SysUISingleton
+                   ~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testProvidesBroadcastReceiverAsSingleton() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                    package test.pkg
+
+                    import android.content.BroadcastReceiver
+                    import com.android.systemui.dagger.SysUISingleton
+                    import dagger.Provides
+                    import dagger.Module
+                    import dagger.multibindings.ClassKey
+                    import dagger.multibindings.IntoMap
+
+                    @Module
+                    abstract class BadModule {
+                       @SysUISingleton
+                       @Provides
+                       @IntoMap
+                       @ClassKey(SingletonBroadcastReceiver::class)
+                       fun providesSingletonBroadcastReceiver(br: SingletonBroadcastReceiver): BroadcastReceiver {
+                          return br
+                       }
+                    }
+                """
+                        .trimIndent()
+                ),
+                *stubs
+            )
+            .issues(SingletonAndroidComponentDetector.ISSUE)
+            .run()
+            .expect(
+                """
+                src/test/pkg/BadModule.kt:12: Error: Do not bind Activities, Services, or BroadcastReceivers as Singleton. [SingletonAndroidComponent]
+                   @SysUISingleton
+                   ~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """
+                    .trimIndent()
+            )
+    }
+    @Test
+    fun testMarksActivityAsSingleton() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                    package test.pkg
+
+                    import android.app.Activity
+                    import com.android.systemui.dagger.SysUISingleton
+
+                    @SysUISingleton
+                    class BadActivity : Activity() {
+                    }
+                """
+                        .trimIndent()
+                ),
+                *stubs
+            )
+            .issues(SingletonAndroidComponentDetector.ISSUE)
+            .run()
+            .expect(
+                """
+                src/test/pkg/BadActivity.kt:6: Error: Do not mark Activities or Services as Singleton. [SingletonAndroidComponent]
+                @SysUISingleton
+                ~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """
+                    .trimIndent()
+            )
+    }
+    @Test
+    fun testMarksBroadcastReceiverAsSingleton() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                    package test.pkg
+
+                    import android.content.BroadcastReceiver
+                    import com.android.systemui.dagger.SysUISingleton
+
+                    @SysUISingleton
+                    class SingletonReceveiver : BroadcastReceiver() {
+                    }
+                """
+                        .trimIndent()
+                ),
+                *stubs
+            )
+            .issues(SingletonAndroidComponentDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    // Define stubs for Android imports. The tests don't run on Android so
+    // they don't "see" any of Android specific classes. We need to define
+    // the method parameters for proper resolution.
+    private val singletonStub: TestFile =
+        java(
+            """
+        package com.android.systemui.dagger;
+
+        public @interface SysUISingleton {
+        }
+        """
+        )
+
+    private val stubs = arrayOf(singletonStub) + androidStubs
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt
new file mode 100644
index 0000000..dc58919
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.scene.session.shared
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+
+/** Data store for [Session][com.android.systemui.scene.session.ui.composable.Session]. */
+class SessionStorage {
+    private var _storage by mutableStateOf(hashMapOf<String, StorageEntry>())
+
+    /**
+     * Data store containing all state retained for invocations of
+     * [rememberSession][com.android.systemui.scene.session.ui.composable.Session.rememberSession]
+     */
+    val storage: MutableMap<String, StorageEntry>
+        get() = _storage
+
+    /**
+     * Storage for an individual invocation of
+     * [rememberSession][com.android.systemui.scene.session.ui.composable.Session.rememberSession]
+     */
+    class StorageEntry(val keys: Array<out Any?>, var stored: Any?)
+
+    /** Clears the data store; any downstream usage within `@Composable`s will be recomposed. */
+    fun clear() {
+        _storage = hashMapOf()
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt
new file mode 100644
index 0000000..924aa54
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt
@@ -0,0 +1,270 @@
+/*
+ * 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.scene.session.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.currentCompositeKeyHash
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.runtime.saveable.mapSaver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import com.android.systemui.scene.session.shared.SessionStorage
+import com.android.systemui.util.kotlin.mapValuesNotNullTo
+
+/**
+ * An explicit storage for remembering composable state outside of the lifetime of a composition.
+ *
+ * Specifically, this allows easy conversion of standard
+ * [remember][androidx.compose.runtime.remember] invocations to ones that are preserved beyond the
+ * callsite's existence in the composition.
+ *
+ * ```kotlin
+ * @Composable
+ * fun Parent() {
+ *   val session = remember { Session() }
+ *   ...
+ *   if (someCondition) {
+ *     Child(session)
+ *   }
+ * }
+ *
+ * @Composable
+ * fun Child(session: Session) {
+ *   val state by session.rememberSession { mutableStateOf(0f) }
+ *   ...
+ * }
+ * ```
+ */
+interface Session {
+    /**
+     * Remember the value returned by [init] if all [inputs] are equal (`==`) to the values they had
+     * in the previous composition, otherwise produce and remember a new value by calling [init].
+     *
+     * @param inputs A set of inputs such that, when any of them have changed, will cause the state
+     *   to reset and [init] to be rerun
+     * @param key An optional key to be used as a key for the saved value. If `null`, we use the one
+     *   automatically generated by the Compose runtime which is unique for the every exact code
+     *   location in the composition tree
+     * @param init A factory function to create the initial value of this state
+     * @see androidx.compose.runtime.remember
+     */
+    @Composable fun <T> rememberSession(key: String?, vararg inputs: Any?, init: () -> T): T
+}
+
+/** Returns a new [Session], optionally backed by the provided [SessionStorage]. */
+fun Session(storage: SessionStorage = SessionStorage()): Session = SessionImpl(storage)
+
+/**
+ * Remember the value returned by [init] if all [inputs] are equal (`==`) to the values they had in
+ * the previous composition, otherwise produce and remember a new value by calling [init].
+ *
+ * @param inputs A set of inputs such that, when any of them have changed, will cause the state to
+ *   reset and [init] to be rerun
+ * @param key An optional key to be used as a key for the saved value. If not provided we use the
+ *   one automatically generated by the Compose runtime which is unique for the every exact code
+ *   location in the composition tree
+ * @param init A factory function to create the initial value of this state
+ * @see androidx.compose.runtime.remember
+ */
+@Composable
+fun <T> Session.rememberSession(vararg inputs: Any?, key: String? = null, init: () -> T): T =
+    rememberSession(key, inputs, init = init)
+
+/**
+ * An explicit storage for remembering composable state outside of the lifetime of a composition.
+ *
+ * Specifically, this allows easy conversion of standard [rememberSession] invocations to ones that
+ * are preserved beyond the callsite's existence in the composition.
+ *
+ * ```kotlin
+ * @Composable
+ * fun Parent() {
+ *   val session = rememberSaveableSession()
+ *   ...
+ *   if (someCondition) {
+ *     Child(session)
+ *   }
+ * }
+ *
+ * @Composable
+ * fun Child(session: SaveableSession) {
+ *   val state by session.rememberSaveableSession { mutableStateOf(0f) }
+ *   ...
+ * }
+ * ```
+ */
+interface SaveableSession : Session {
+    /**
+     * Remember the value produced by [init].
+     *
+     * It behaves similarly to [rememberSession], but the stored value will survive the activity or
+     * process recreation using the saved instance state mechanism (for example it happens when the
+     * screen is rotated in the Android application).
+     *
+     * @param inputs A set of inputs such that, when any of them have changed, will cause the state
+     *   to reset and [init] to be rerun
+     * @param saver The [Saver] object which defines how the state is saved and restored.
+     * @param key An optional key to be used as a key for the saved value. If not provided we use
+     *   the automatically generated by the Compose runtime which is unique for the every exact code
+     *   location in the composition tree
+     * @param init A factory function to create the initial value of this state
+     * @see rememberSaveable
+     */
+    @Composable
+    fun <T : Any> rememberSaveableSession(
+        vararg inputs: Any?,
+        saver: Saver<T, out Any>,
+        key: String?,
+        init: () -> T,
+    ): T
+}
+
+/**
+ * Returns a new [SaveableSession] that is preserved across configuration changes.
+ *
+ * @param inputs A set of inputs such that, when any of them have changed, will cause the state to
+ *   reset.
+ * @param key An optional key to be used as a key for the saved value. If not provided we use the
+ *   automatically generated by the Compose runtime which is unique for the every exact code
+ *   location in the composition tree.
+ */
+@Composable
+fun rememberSaveableSession(
+    vararg inputs: Any?,
+    key: String? = null,
+): SaveableSession =
+    rememberSaveable(inputs, SaveableSessionImpl.SessionSaver, key) { SaveableSessionImpl() }
+
+private class SessionImpl(
+    private val storage: SessionStorage = SessionStorage(),
+) : Session {
+    @Composable
+    override fun <T> rememberSession(key: String?, vararg inputs: Any?, init: () -> T): T {
+        val storage = storage.storage
+        val compositeKey = currentCompositeKeyHash
+        // key is the one provided by the user or the one generated by the compose runtime
+        val finalKey =
+            if (!key.isNullOrEmpty()) {
+                key
+            } else {
+                compositeKey.toString(MAX_SUPPORTED_RADIX)
+            }
+        if (finalKey !in storage) {
+            val value = init()
+            SideEffect { storage[finalKey] = SessionStorage.StorageEntry(inputs, value) }
+            return value
+        }
+        val entry = storage[finalKey]!!
+        if (!inputs.contentEquals(entry.keys)) {
+            val value = init()
+            SideEffect { entry.stored = value }
+            return value
+        }
+        @Suppress("UNCHECKED_CAST") return entry.stored as T
+    }
+}
+
+private class SaveableSessionImpl(
+    saveableStorage: MutableMap<String, StorageEntry> = mutableMapOf(),
+    sessionStorage: SessionStorage = SessionStorage(),
+) : SaveableSession, Session by Session(sessionStorage) {
+
+    var saveableStorage: MutableMap<String, StorageEntry> by mutableStateOf(saveableStorage)
+
+    @Composable
+    override fun <T : Any> rememberSaveableSession(
+        vararg inputs: Any?,
+        saver: Saver<T, out Any>,
+        key: String?,
+        init: () -> T,
+    ): T {
+        val compositeKey = currentCompositeKeyHash
+        // key is the one provided by the user or the one generated by the compose runtime
+        val finalKey =
+            if (!key.isNullOrEmpty()) {
+                key
+            } else {
+                compositeKey.toString(MAX_SUPPORTED_RADIX)
+            }
+
+        @Suppress("UNCHECKED_CAST") (saver as Saver<T, Any>)
+
+        if (finalKey !in saveableStorage) {
+            val value = init()
+            SideEffect { saveableStorage[finalKey] = StorageEntry.Restored(inputs, value, saver) }
+            return value
+        }
+        when (val entry = saveableStorage[finalKey]!!) {
+            is StorageEntry.Unrestored -> {
+                val value = saver.restore(entry.unrestored) ?: init()
+                SideEffect {
+                    saveableStorage[finalKey] = StorageEntry.Restored(inputs, value, saver)
+                }
+                return value
+            }
+            is StorageEntry.Restored<*> -> {
+                if (!inputs.contentEquals(entry.inputs)) {
+                    val value = init()
+                    SideEffect {
+                        saveableStorage[finalKey] = StorageEntry.Restored(inputs, value, saver)
+                    }
+                    return value
+                }
+                @Suppress("UNCHECKED_CAST") return entry.stored as T
+            }
+        }
+    }
+
+    sealed class StorageEntry {
+        class Unrestored(val unrestored: Any) : StorageEntry()
+
+        class Restored<T>(val inputs: Array<out Any?>, var stored: T, val saver: Saver<T, Any>) :
+            StorageEntry() {
+            fun SaverScope.saveEntry() {
+                with(saver) { stored?.let { save(it) } }
+            }
+        }
+    }
+
+    object SessionSaver :
+        Saver<SaveableSessionImpl, Any> by mapSaver(
+            save = { sessionScope: SaveableSessionImpl ->
+                sessionScope.saveableStorage.mapValues { (k, v) ->
+                    when (v) {
+                        is StorageEntry.Unrestored -> v.unrestored
+                        is StorageEntry.Restored<*> -> {
+                            with(v) { saveEntry() }
+                        }
+                    }
+                }
+            },
+            restore = { savedMap: Map<String, Any?> ->
+                SaveableSessionImpl(
+                    saveableStorage =
+                        savedMap.mapValuesNotNullTo(mutableMapOf()) { (k, v) ->
+                            v?.let { StorageEntry.Unrestored(v) }
+                        }
+                )
+            }
+        )
+}
+
+private const val MAX_SUPPORTED_RADIX = 36
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
index 00225fc..0f6d51d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
@@ -54,6 +54,13 @@
     override fun VolumePanelComposeScope.Content(modifier: Modifier) {
         val slice by viewModel.buttonSlice.collectAsState()
         val label = stringResource(R.string.volume_panel_noise_control_title)
+        val isClickable = viewModel.isClickable(slice)
+        val onClick =
+            if (isClickable) {
+                { ancPopup.show(null) }
+            } else {
+                null
+            }
         Column(
             modifier = modifier,
             verticalArrangement = Arrangement.spacedBy(12.dp),
@@ -69,8 +76,9 @@
                         }
                         .clip(RoundedCornerShape(28.dp)),
                 slice = slice,
+                isEnabled = onClick != null,
                 onWidthChanged = viewModel::onButtonSliceWidthChanged,
-                onClick = { ancPopup.show(null) }
+                onClick = onClick,
             )
             Text(
                 modifier = Modifier.clearAndSetSemantics {},
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
index 74af3ca..fc5d212 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
@@ -32,6 +32,7 @@
 fun SliceAndroidView(
     slice: Slice?,
     modifier: Modifier = Modifier,
+    isEnabled: Boolean = true,
     onWidthChanged: ((Int) -> Unit)? = null,
     onClick: (() -> Unit)? = null,
 ) {
@@ -40,7 +41,6 @@
         factory = { context: Context ->
             ClickableSliceView(
                     ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel),
-                    onClick,
                 )
                 .apply {
                     mode = SliceView.MODE_LARGE
@@ -50,12 +50,14 @@
                     if (onWidthChanged != null) {
                         addOnLayoutChangeListener(OnWidthChangedLayoutListener(onWidthChanged))
                     }
-                    if (onClick != null) {
-                        setOnClickListener { onClick() }
-                    }
                 }
         },
-        update = { sliceView: SliceView -> sliceView.slice = slice }
+        update = { sliceView: ClickableSliceView ->
+            sliceView.slice = slice
+            sliceView.onClick = onClick
+            sliceView.isEnabled = isEnabled
+            sliceView.isClickable = isEnabled
+        }
     )
 }
 
@@ -86,10 +88,9 @@
  * first.
  */
 @SuppressLint("ViewConstructor") // only used in this class
-private class ClickableSliceView(
-    context: Context,
-    private val onClick: (() -> Unit)?,
-) : SliceView(context) {
+private class ClickableSliceView(context: Context) : SliceView(context) {
+
+    var onClick: (() -> Unit)? = null
 
     init {
         if (onClick != null) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 874c0a2..12debbc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -19,6 +19,7 @@
 import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
@@ -91,7 +92,8 @@
                         },
                     onClick = { onCheckedChange(!viewModel.isActive) },
                     shape = RoundedCornerShape(28.dp),
-                    colors = colors
+                    colors = colors,
+                    contentPadding = PaddingValues(0.dp)
                 ) {
                     Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
                 }
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 7f94f0d..4e3a032 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
@@ -117,6 +117,9 @@
         coroutineScope: CoroutineScope,
         transitionKey: TransitionKey? = null,
     ): TransitionState.Transition?
+
+    /** Immediately snap to the given [scene]. */
+    fun snapToScene(scene: SceneKey)
 }
 
 /**
@@ -745,6 +748,17 @@
     override fun CoroutineScope.onChangeScene(scene: SceneKey) {
         setTargetScene(scene, coroutineScope = this)
     }
+
+    override fun snapToScene(scene: SceneKey) {
+        // Force finish all transitions.
+        while (currentTransitions.isNotEmpty()) {
+            val transition = transitionStates[0] as TransitionState.Transition
+            finishTransition(transition, transition.currentScene)
+        }
+
+        check(transitionStates.size == 1)
+        transitionStates[0] = TransitionState.Idle(scene)
+    }
 }
 
 private const val TAG = "SceneTransitionLayoutState"
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 f29d0a7..d2c8bd6 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
@@ -635,4 +635,19 @@
             Log.setWtfHandler(originalHandler)
         }
     }
+
+    @Test
+    fun snapToScene() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutState(SceneA)
+
+        // Transition to B.
+        state.setTargetScene(SceneB, coroutineScope = this)
+        val transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasCurrentScene(SceneB)
+
+        // Snap to C.
+        state.snapToScene(SceneC)
+        assertThat(state.transitionState).isIdle()
+        assertThat(state.transitionState).hasCurrentScene(SceneC)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index c8717d8..447c280 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -17,8 +17,6 @@
 
 package com.android.keyguard
 
-import android.app.admin.DevicePolicyManager
-import android.app.admin.flags.Flags as DevicePolicyFlags
 import android.content.res.Configuration
 import android.media.AudioManager
 import android.telephony.TelephonyManager
@@ -150,7 +148,6 @@
     @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
     @Mock private lateinit var postureController: DevicePostureController
-    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
 
     @Captor
     private lateinit var swipeListenerArgumentCaptor:
@@ -276,7 +273,6 @@
                 mSelectedUserInteractor,
                 deviceProvisionedController,
                 faceAuthAccessibilityDelegate,
-                devicePolicyManager,
                 keyguardTransitionInteractor,
                 { primaryBouncerInteractor },
             ) {
@@ -938,45 +934,6 @@
         verify(viewFlipperController).asynchronouslyInflateView(any(), any(), any())
     }
 
-    @Test
-    fun showAlmostAtWipeDialog_calledOnMainUser_setsCorrectUserType() {
-        mSetFlagsRule.enableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES)
-        val mainUserId = 10
-
-        underTest.showMessageForFailedUnlockAttempt(
-            /* userId = */ mainUserId,
-            /* expiringUserId = */ mainUserId,
-            /* mainUserId = */ mainUserId,
-            /* remainingBeforeWipe = */ 1,
-            /* failedAttempts = */ 1
-        )
-
-        verify(view)
-            .showAlmostAtWipeDialog(any(), any(), eq(KeyguardSecurityContainer.USER_TYPE_PRIMARY))
-    }
-
-    @Test
-    fun showAlmostAtWipeDialog_calledOnNonMainUser_setsCorrectUserType() {
-        mSetFlagsRule.enableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES)
-        val secondaryUserId = 10
-        val mainUserId = 0
-
-        underTest.showMessageForFailedUnlockAttempt(
-            /* userId = */ secondaryUserId,
-            /* expiringUserId = */ secondaryUserId,
-            /* mainUserId = */ mainUserId,
-            /* remainingBeforeWipe = */ 1,
-            /* failedAttempts = */ 1
-        )
-
-        verify(view)
-            .showAlmostAtWipeDialog(
-                any(),
-                any(),
-                eq(KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER)
-            )
-    }
-
     private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener
         get() {
             underTest.onViewAttached()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt
new file mode 100644
index 0000000..c0d481c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.accessibility.data.repository
+
+import android.os.UserHandle
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
+class OneHandedModeRepositoryImplTest : SysuiTestCase() {
+
+    private val testUser1 = UserHandle.of(1)!!
+    private val testUser2 = UserHandle.of(2)!!
+    private val testDispatcher = StandardTestDispatcher()
+    private val scope = TestScope(testDispatcher)
+    private val settings: FakeSettings = FakeSettings()
+
+    private val underTest: OneHandedModeRepository =
+        OneHandedModeRepositoryImpl(
+            testDispatcher,
+            scope.backgroundScope,
+            settings,
+        )
+
+    @Test
+    fun isEnabled_settingNotInitialized_returnsFalseByDefault() =
+        scope.runTest {
+            val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+            runCurrent()
+
+            assertThat(actualValue).isFalse()
+        }
+
+    @Test
+    fun isEnabled_initiallyGetsSettingsValue() =
+        scope.runTest {
+            val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+            settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
+            runCurrent()
+
+            assertThat(actualValue).isTrue()
+        }
+
+    @Test
+    fun isEnabled_settingUpdated_valueUpdated() =
+        scope.runTest {
+            val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+            runCurrent()
+            assertThat(actualValue).isFalse()
+
+            settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
+            runCurrent()
+
+            assertThat(actualValue).isTrue()
+            runCurrent()
+
+            settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+            runCurrent()
+            assertThat(actualValue).isFalse()
+        }
+
+    @Test
+    fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() =
+        scope.runTest {
+            val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
+            val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
+
+            settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+            settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier)
+            runCurrent()
+            assertThat(lastValueUser1).isFalse()
+            assertThat(lastValueUser2).isFalse()
+
+            settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
+            runCurrent()
+            assertThat(lastValueUser1).isTrue()
+            assertThat(lastValueUser2).isFalse()
+        }
+
+    @Test
+    fun setEnabled() =
+        scope.runTest {
+            val success = underTest.setIsEnabled(true, testUser1)
+            runCurrent()
+            assertThat(success).isTrue()
+
+            val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
+            assertThat(actualValue).isEqualTo(ENABLED)
+        }
+
+    @Test
+    fun setDisabled() =
+        scope.runTest {
+            val success = underTest.setIsEnabled(false, testUser1)
+            runCurrent()
+            assertThat(success).isTrue()
+
+            val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
+            assertThat(actualValue).isEqualTo(DISABLED)
+        }
+
+    companion object {
+        private const val SETTING_NAME = Settings.Secure.ONE_HANDED_MODE_ENABLED
+        private const val DISABLED = 0
+        private const val ENABLED = 1
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 0c5e726..81878aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -17,8 +17,6 @@
 package com.android.systemui.authentication.domain.interactor
 
 import android.app.admin.DevicePolicyManager
-import android.app.admin.flags.Flags as DevicePolicyFlags
-import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
@@ -34,8 +32,6 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.data.repository.fakeUserRepository
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -414,16 +410,12 @@
         }
 
     @Test
-    @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES)
     fun upcomingWipe() =
         testScope.runTest {
             val upcomingWipe by collectLastValue(underTest.upcomingWipe)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
             val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
-            kosmos.fakeUserRepository.asMainUser()
-            kosmos.fakeAuthenticationRepository.profileWithMinFailedUnlockAttemptsForWipe =
-                FakeUserRepository.MAIN_USER_ID
 
             underTest.authenticate(correctPin)
             assertThat(upcomingWipe).isNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
index 31b67b4..f52c66e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
@@ -16,36 +16,57 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @ExperimentalCoroutinesApi
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class AodToLockscreenTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
     val kosmos = testKosmos()
     val testScope = kosmos.testScope
     val repository = kosmos.fakeKeyguardTransitionRepository
-    val shadeRepository = kosmos.fakeShadeRepository
+    val shadeTestUtil by lazy { kosmos.shadeTestUtil }
     val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
-    val underTest = kosmos.aodToLockscreenTransitionViewModel
+    lateinit var underTest: AodToLockscreenTransitionViewModel
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags!!)
+    }
+
+    @Before
+    fun setup() {
+        underTest = kosmos.aodToLockscreenTransitionViewModel
+    }
 
     @Test
     fun deviceEntryParentViewShows() =
@@ -65,7 +86,7 @@
         testScope.runTest {
             val alpha by collectLastValue(underTest.notificationAlpha)
 
-            shadeRepository.setQsExpansion(0.5f)
+            shadeTestUtil.setQsExpansion(0.5f)
             runCurrent()
 
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -81,7 +102,7 @@
         testScope.runTest {
             val alpha by collectLastValue(underTest.notificationAlpha)
 
-            shadeRepository.setQsExpansion(0f)
+            shadeTestUtil.setQsExpansion(0f)
             runCurrent()
 
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
index bef9515..e3ae3ba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
@@ -16,13 +16,14 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -32,31 +33,53 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @ExperimentalCoroutinesApi
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockscreenToAodTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) }
         }
     private val testScope = kosmos.testScope
     private val repository = kosmos.fakeKeyguardTransitionRepository
-    private val shadeRepository = kosmos.shadeRepository
     private val keyguardRepository = kosmos.fakeKeyguardRepository
     private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
     private val biometricSettingsRepository = kosmos.biometricSettingsRepository
-    private val underTest = kosmos.lockscreenToAodTransitionViewModel
+
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+    lateinit var underTest: LockscreenToAodTransitionViewModel
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags!!)
+    }
+
+    @Before
+    fun setup() {
+        underTest = kosmos.lockscreenToAodTransitionViewModel
+    }
 
     @Test
     fun backgroundViewAlpha_shadeNotExpanded() =
@@ -195,11 +218,11 @@
 
     private fun shadeExpanded(expanded: Boolean) {
         if (expanded) {
-            shadeRepository.setQsExpansion(1f)
+            shadeTestUtil.setQsExpansion(1f)
         } else {
             keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-            shadeRepository.setQsExpansion(0f)
-            shadeRepository.setLockscreenShadeExpansion(0f)
+            shadeTestUtil.setQsExpansion(0f)
+            shadeTestUtil.setLockscreenShadeExpansion(0f)
         }
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index 8f04ec38..adeb395 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -18,24 +18,25 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.ShadeTestUtil
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
@@ -45,10 +46,12 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
 
     private val kosmos =
         testKosmos().apply {
@@ -56,14 +59,27 @@
         }
     private val testScope = kosmos.testScope
     private lateinit var repository: FakeKeyguardTransitionRepository
-    private lateinit var shadeRepository: ShadeRepository
+    private lateinit var shadeTestUtil: ShadeTestUtil
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var underTest: LockscreenToDreamingTransitionViewModel
 
+    // add to init block
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags!!)
+    }
+
     @Before
     fun setUp() {
         repository = kosmos.fakeKeyguardTransitionRepository
-        shadeRepository = kosmos.shadeRepository
+        shadeTestUtil = kosmos.shadeTestUtil
         keyguardRepository = kosmos.fakeKeyguardRepository
         underTest = kosmos.lockscreenToDreamingTransitionViewModel
     }
@@ -177,11 +193,11 @@
 
     private fun shadeExpanded(expanded: Boolean) {
         if (expanded) {
-            shadeRepository.setQsExpansion(1f)
+            shadeTestUtil.setQsExpansion(1f)
         } else {
             keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-            shadeRepository.setQsExpansion(0f)
-            shadeRepository.setLockscreenShadeExpansion(0f)
+            shadeTestUtil.setQsExpansion(0f)
+            shadeTestUtil.setLockscreenShadeExpansion(0f)
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index b120f87..f8da74f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -18,27 +18,28 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.ShadeTestUtil
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
@@ -48,25 +49,40 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
     private val testScope = kosmos.testScope
     private lateinit var repository: FakeKeyguardTransitionRepository
-    private lateinit var shadeRepository: ShadeRepository
+    private lateinit var shadeTestUtil: ShadeTestUtil
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var configurationRepository: FakeConfigurationRepository
     private lateinit var underTest: LockscreenToOccludedTransitionViewModel
 
+    // add to init block
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags!!)
+    }
+
     @Before
     fun setUp() {
         repository = kosmos.fakeKeyguardTransitionRepository
-        shadeRepository = kosmos.shadeRepository
+        shadeTestUtil = kosmos.shadeTestUtil
         keyguardRepository = kosmos.fakeKeyguardRepository
         configurationRepository = kosmos.fakeConfigurationRepository
         underTest = kosmos.lockscreenToOccludedTransitionViewModel
@@ -200,11 +216,11 @@
 
     private fun shadeExpanded(expanded: Boolean) {
         if (expanded) {
-            shadeRepository.setQsExpansion(1f)
+            shadeTestUtil.setQsExpansion(1f)
         } else {
             keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-            shadeRepository.setQsExpansion(0f)
-            shadeRepository.setLockscreenShadeExpansion(0f)
+            shadeTestUtil.setQsExpansion(0f)
+            shadeTestUtil.setLockscreenShadeExpansion(0f)
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index 43ab93a..d5df159 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -16,11 +16,12 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -29,29 +30,50 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
 import com.google.common.collect.Range
 import com.google.common.truth.Truth
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @ExperimentalCoroutinesApi
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockscreenToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization?) :
+    SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
     private val testScope = kosmos.testScope
     private val repository = kosmos.fakeKeyguardTransitionRepository
-    private val shadeRepository = kosmos.shadeRepository
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
     private val keyguardRepository = kosmos.fakeKeyguardRepository
-    private val underTest = kosmos.lockscreenToPrimaryBouncerTransitionViewModel
+    private lateinit var underTest: LockscreenToPrimaryBouncerTransitionViewModel
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags!!)
+    }
+
+    @Before
+    fun setup() {
+        underTest = kosmos.lockscreenToPrimaryBouncerTransitionViewModel
+    }
 
     @Test
     fun deviceEntryParentViewAlpha_shadeExpanded() =
@@ -119,11 +141,11 @@
 
     private fun shadeExpanded(expanded: Boolean) {
         if (expanded) {
-            shadeRepository.setQsExpansion(1f)
+            shadeTestUtil.setQsExpansion(1f)
         } else {
             keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-            shadeRepository.setQsExpansion(0f)
-            shadeRepository.setLockscreenShadeExpansion(0f)
+            shadeTestUtil.setQsExpansion(0f)
+            shadeTestUtil.setLockscreenShadeExpansion(0f)
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt
new file mode 100644
index 0000000..0761ee7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.qs.tiles.impl.onehanded.domain.interactor
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.oneHandedModeRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileDataInteractor
+import com.android.wm.shell.onehanded.OneHanded
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class OneHandedModeTileDataInteractorTest : SysuiTestCase() {
+
+    private val kosmos = Kosmos()
+    private val testUser = UserHandle.of(1)!!
+    private val oneHandedModeRepository = kosmos.oneHandedModeRepository
+    private val underTest: OneHandedModeTileDataInteractor =
+        OneHandedModeTileDataInteractor(oneHandedModeRepository)
+
+    @Test
+    fun availability_matchesController() = runTest {
+        val expectedAvailability = OneHanded.sIsSupportOneHandedMode
+        val availability by collectLastValue(underTest.availability(testUser))
+
+        assertThat(availability).isEqualTo(expectedAvailability)
+    }
+
+    @Test
+    fun data_matchesRepository() = runTest {
+        val lastData by
+            collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+        runCurrent()
+        assertThat(lastData!!.isEnabled).isFalse()
+
+        oneHandedModeRepository.setIsEnabled(true, testUser)
+        runCurrent()
+        assertThat(lastData!!.isEnabled).isTrue()
+
+        oneHandedModeRepository.setIsEnabled(false, testUser)
+        runCurrent()
+        assertThat(lastData!!.isEnabled).isFalse()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..3f17d4c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileUserActionInteractorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.qs.tiles.impl.onehanded.domain.interactor
+
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.FakeOneHandedModeRepository
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class OneHandedModeTileUserActionInteractorTest : SysuiTestCase() {
+
+    private val testUser = UserHandle.of(1)
+    private val repository = FakeOneHandedModeRepository()
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+
+    private val underTest =
+        OneHandedModeTileUserActionInteractor(
+            repository,
+            inputHandler,
+        )
+
+    @Test
+    fun handleClickWhenEnabled() = runTest {
+        val wasEnabled = true
+        repository.setIsEnabled(wasEnabled, testUser)
+
+        underTest.handleInput(
+            QSTileInputTestKtx.click(OneHandedModeTileModel(wasEnabled), testUser)
+        )
+
+        assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled)
+    }
+
+    @Test
+    fun handleClickWhenDisabled() = runTest {
+        val wasEnabled = false
+        repository.setIsEnabled(wasEnabled, testUser)
+
+        underTest.handleInput(
+            QSTileInputTestKtx.click(OneHandedModeTileModel(wasEnabled), testUser)
+        )
+
+        assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled)
+    }
+
+    @Test
+    fun handleLongClickWhenDisabled() = runTest {
+        val enabled = false
+
+        underTest.handleInput(
+            QSTileInputTestKtx.longClick(OneHandedModeTileModel(enabled), testUser)
+        )
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_ONE_HANDED_SETTINGS)
+        }
+    }
+
+    @Test
+    fun handleLongClickWhenEnabled() = runTest {
+        val enabled = true
+
+        underTest.handleInput(
+            QSTileInputTestKtx.longClick(OneHandedModeTileModel(enabled), testUser)
+        )
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_ONE_HANDED_SETTINGS)
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
new file mode 100644
index 0000000..7ef020d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.qs.tiles.impl.onehanded.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tileimpl.SubtitleArrayMapping
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.android.systemui.qs.tiles.impl.onehanded.qsOneHandedModeTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class OneHandedModeTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val config = kosmos.qsOneHandedModeTileConfig
+    private val subtitleArrayId = SubtitleArrayMapping.getSubtitleId(config.tileSpec.spec)
+    private val subtitleArray by lazy { context.resources.getStringArray(subtitleArrayId) }
+
+    private lateinit var mapper: OneHandedModeTileMapper
+
+    @Before
+    fun setup() {
+        mapper =
+            OneHandedModeTileMapper(
+                context.orCreateTestableResources
+                    .apply {
+                        addOverride(
+                            com.android.internal.R.drawable.ic_qs_one_handed_mode,
+                            TestStubDrawable()
+                        )
+                    }
+                    .resources,
+                context.theme
+            )
+    }
+
+    @Test
+    fun disabledModel() {
+        val inputModel = OneHandedModeTileModel(false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createOneHandedModeTileState(
+                QSTileState.ActivationState.INACTIVE,
+                subtitleArray[1],
+                com.android.internal.R.drawable.ic_qs_one_handed_mode
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel() {
+        val inputModel = OneHandedModeTileModel(true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createOneHandedModeTileState(
+                QSTileState.ActivationState.ACTIVE,
+                subtitleArray[2],
+                com.android.internal.R.drawable.ic_qs_one_handed_mode
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createOneHandedModeTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String,
+        iconRes: Int,
+    ): QSTileState {
+        val label = context.getString(R.string.quick_settings_onehanded_label)
+        return QSTileState(
+            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            label,
+            null,
+            QSTileState.SideViewIcon.None,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
similarity index 76%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index 78b7615..cbbc4d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -18,116 +18,92 @@
 
 import android.graphics.Rect
 import android.graphics.drawable.Icon
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.DarkIconDispatcher
-import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
-import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
-import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.headsUpNotificationIconViewStateRepository
 import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
-import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepository
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
-import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.statusbar.phone.data.repository.fakeDarkIconRepository
+import com.android.systemui.statusbar.phone.dozeParameters
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.ui.isAnimating
 import com.android.systemui.util.ui.value
 import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterization?) :
+    SysuiTestCase() {
 
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                BiometricsDomainLayerModule::class,
-                UserDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<NotificationIconContainerStatusBarViewModel> {
-
-        val activeNotificationsRepository: ActiveNotificationListRepository
-        val darkIconRepository: FakeDarkIconRepository
-        val deviceProvisioningRepository: FakeDeviceProvisioningRepository
-        val headsUpViewStateRepository: HeadsUpNotificationIconViewStateRepository
-        val keyguardTransitionRepository: FakeKeyguardTransitionRepository
-        val keyguardRepository: FakeKeyguardRepository
-        val powerRepository: FakePowerRepository
-        val shadeRepository: FakeShadeRepository
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                mocks: TestMocksModule,
-                featureFlags: FakeFeatureFlagsClassicModule,
-            ): TestComponent
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
         }
     }
 
-    private val dozeParams: DozeParameters = mock()
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags!!)
+    }
 
-    private val testComponent: TestComponent =
-        DaggerNotificationIconContainerStatusBarViewModelTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        set(Flags.FULL_SCREEN_USER_SWITCHER, value = false)
-                    },
-                mocks =
-                    TestMocksModule(
-                        dozeParameters = dozeParams,
-                    ),
-            )
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val powerRepository = kosmos.fakePowerRepository
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val darkIconRepository = kosmos.fakeDarkIconRepository
+    private val headsUpViewStateRepository = kosmos.headsUpNotificationIconViewStateRepository
+    private val activeNotificationsRepository = kosmos.activeNotificationListRepository
+
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+    private val dozeParams = kosmos.dozeParameters
+
+    lateinit var underTest: NotificationIconContainerStatusBarViewModel
 
     @Before
     fun setup() {
-        testComponent.apply {
-            keyguardRepository.setKeyguardShowing(false)
-            powerRepository.updateWakefulness(
-                rawState = WakefulnessState.AWAKE,
-                lastWakeReason = WakeSleepReason.OTHER,
-                lastSleepReason = WakeSleepReason.OTHER,
-            )
-        }
+        underTest = kosmos.notificationIconContainerStatusBarViewModel
+        keyguardRepository.setKeyguardShowing(false)
+        powerRepository.updateWakefulness(
+            rawState = WakefulnessState.AWAKE,
+            lastWakeReason = WakeSleepReason.OTHER,
+            lastSleepReason = WakeSleepReason.OTHER,
+        )
     }
 
     @Test
     fun animationsEnabled_isFalse_whenDeviceAsleepAndNotPulsing() =
-        testComponent.runTest {
+        testScope.runTest {
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.ASLEEP,
                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -150,7 +126,7 @@
 
     @Test
     fun animationsEnabled_isTrue_whenDeviceAsleepAndPulsing() =
-        testComponent.runTest {
+        testScope.runTest {
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.ASLEEP,
                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -173,7 +149,7 @@
 
     @Test
     fun animationsEnabled_isFalse_whenStartingToSleepAndNotControlScreenOff() =
-        testComponent.runTest {
+        testScope.runTest {
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.STARTING_TO_SLEEP,
                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -194,7 +170,7 @@
 
     @Test
     fun animationsEnabled_isTrue_whenStartingToSleepAndControlScreenOff() =
-        testComponent.runTest {
+        testScope.runTest {
             val animationsEnabled by collectLastValue(underTest.animationsEnabled)
             assertThat(animationsEnabled).isTrue()
 
@@ -218,7 +194,7 @@
 
     @Test
     fun animationsEnabled_isTrue_whenNotAsleep() =
-        testComponent.runTest {
+        testScope.runTest {
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.AWAKE,
                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -236,7 +212,7 @@
 
     @Test
     fun animationsEnabled_isTrue_whenKeyguardIsNotShowing() =
-        testComponent.runTest {
+        testScope.runTest {
             val animationsEnabled by collectLastValue(underTest.animationsEnabled)
 
             keyguardTransitionRepository.sendTransitionStep(
@@ -257,7 +233,7 @@
 
     @Test
     fun iconColors_testsDarkBounds() =
-        testComponent.runTest {
+        testScope.runTest {
             darkIconRepository.darkState.value =
                 SysuiDarkIconDispatcher.DarkChange(
                     emptyList(),
@@ -280,7 +256,7 @@
 
     @Test
     fun iconColors_staticDrawableColor_notInDarkTintArea() =
-        testComponent.runTest {
+        testScope.runTest {
             darkIconRepository.darkState.value =
                 SysuiDarkIconDispatcher.DarkChange(
                     listOf(Rect(0, 0, 5, 5)),
@@ -295,7 +271,7 @@
 
     @Test
     fun iconColors_notInDarkTintArea() =
-        testComponent.runTest {
+        testScope.runTest {
             darkIconRepository.darkState.value =
                 SysuiDarkIconDispatcher.DarkChange(
                     listOf(Rect(0, 0, 5, 5)),
@@ -309,9 +285,9 @@
 
     @Test
     fun isolatedIcon_animateOnAppear_shadeCollapsed() =
-        testComponent.runTest {
+        testScope.runTest {
             val icon: Icon = mock()
-            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeTestUtil.setShadeExpansion(0f)
             activeNotificationsRepository.activeNotifications.value =
                 ActiveNotificationsStore.Builder()
                     .apply {
@@ -336,9 +312,9 @@
 
     @Test
     fun isolatedIcon_dontAnimateOnAppear_shadeExpanded() =
-        testComponent.runTest {
+        testScope.runTest {
             val icon: Icon = mock()
-            shadeRepository.setLegacyShadeExpansion(.5f)
+            shadeTestUtil.setShadeExpansion(.5f)
             activeNotificationsRepository.activeNotifications.value =
                 ActiveNotificationsStore.Builder()
                     .apply {
@@ -363,7 +339,7 @@
 
     @Test
     fun isolatedIcon_updateWhenIconDataChanges() =
-        testComponent.runTest {
+        testScope.runTest {
             val icon: Icon = mock()
             val isolatedIcon by collectLastValue(underTest.isolatedIcon)
             runCurrent()
@@ -390,7 +366,7 @@
 
     @Test
     fun isolatedIcon_lastMessageIsFromReply_notNull() =
-        testComponent.runTest {
+        testScope.runTest {
             val icon: Icon = mock()
             headsUpViewStateRepository.isolatedNotification.value = "notif1"
             activeNotificationsRepository.activeNotifications.value =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
index dc96139..dddf582 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
@@ -61,6 +61,7 @@
                 AncSliceRepositoryImpl(
                     localMediaRepositoryFactory,
                     testScope.testScheduler,
+                    testScope.testScheduler,
                     sliceViewManager,
                 )
         }
diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml
index 2769bea..73812c9 100644
--- a/packages/SystemUI/res/values-land/styles.xml
+++ b/packages/SystemUI/res/values-land/styles.xml
@@ -39,7 +39,7 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Title">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">6dp</item>
         <item name="android:textSize">36dp</item>
         <item name="android:focusable">true</item>
@@ -47,14 +47,14 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Subtitle">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">6dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Description">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">6dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
diff --git a/packages/SystemUI/res/values-sw600dp-land/styles.xml b/packages/SystemUI/res/values-sw600dp-land/styles.xml
index 0d46cbc..cde1a1373 100644
--- a/packages/SystemUI/res/values-sw600dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/styles.xml
@@ -18,7 +18,7 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
 
     <style name="TextAppearance.AuthNonBioCredential.Title">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">16dp</item>
         <item name="android:textSize">36sp</item>
         <item name="android:focusable">true</item>
@@ -26,14 +26,14 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Subtitle">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">16dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Description">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">16dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
diff --git a/packages/SystemUI/res/values-sw600dp-port/styles.xml b/packages/SystemUI/res/values-sw600dp-port/styles.xml
index 3add566..85e7af6 100644
--- a/packages/SystemUI/res/values-sw600dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/styles.xml
@@ -26,7 +26,7 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Title">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">24dp</item>
         <item name="android:textSize">36sp</item>
         <item name="android:focusable">true</item>
diff --git a/packages/SystemUI/res/values-sw720dp-land/styles.xml b/packages/SystemUI/res/values-sw720dp-land/styles.xml
index 7cdd07b..e75173d 100644
--- a/packages/SystemUI/res/values-sw720dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/styles.xml
@@ -18,7 +18,7 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
 
     <style name="TextAppearance.AuthNonBioCredential.Title">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">16dp</item>
         <item name="android:textSize">36sp</item>
         <item name="android:focusable">true</item>
@@ -26,14 +26,14 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Subtitle">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">16dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Description">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">16dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
diff --git a/packages/SystemUI/res/values-sw720dp-port/styles.xml b/packages/SystemUI/res/values-sw720dp-port/styles.xml
index 3add566..85e7af6 100644
--- a/packages/SystemUI/res/values-sw720dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/styles.xml
@@ -26,7 +26,7 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Title">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">24dp</item>
         <item name="android:textSize">36sp</item>
         <item name="android:focusable">true</item>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 69de45e..2c4cdb9 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -175,21 +175,21 @@
     </style>
 
     <style name="TextAppearance.AuthCredential.OldTitle">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:paddingTop">12dp</item>
         <item name="android:paddingHorizontal">24dp</item>
         <item name="android:textSize">24sp</item>
     </style>
 
     <style name="TextAppearance.AuthCredential.OldSubtitle">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:paddingTop">8dp</item>
         <item name="android:paddingHorizontal">24dp</item>
         <item name="android:textSize">16sp</item>
     </style>
 
     <style name="TextAppearance.AuthCredential.OldDescription">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:paddingTop">8dp</item>
         <item name="android:paddingHorizontal">24dp</item>
         <item name="android:textSize">14sp</item>
@@ -205,7 +205,7 @@
     </style>
 
     <style name="TextAppearance.AuthCredential.Title" parent="TextAppearance.Material3.HeadlineSmall" >
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
@@ -257,7 +257,7 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Title">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">24dp</item>
         <item name="android:textSize">36dp</item>
         <item name="android:focusable">true</item>
@@ -265,14 +265,14 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Subtitle">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">20dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Description">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">20dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index c08b083..69aa909 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -77,7 +77,7 @@
     // settings is expanded.
     public static final int SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1 << 11;
     // Winscope tracing is enabled
-    public static final int SYSUI_STATE_TRACING_ENABLED = 1 << 12;
+    public static final int SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION = 1 << 12;
     // The Assistant gesture should be constrained. It is up to the launcher implementation to
     // decide how to constrain it
     public static final int SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1 << 13;
@@ -148,7 +148,7 @@
             SYSUI_STATE_OVERVIEW_DISABLED,
             SYSUI_STATE_HOME_DISABLED,
             SYSUI_STATE_SEARCH_DISABLED,
-            SYSUI_STATE_TRACING_ENABLED,
+            SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION,
             SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
             SYSUI_STATE_BUBBLES_EXPANDED,
             SYSUI_STATE_DIALOG_SHOWING,
@@ -211,8 +211,8 @@
         if ((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0) {
             str.add("a11y_long_click");
         }
-        if ((flags & SYSUI_STATE_TRACING_ENABLED) != 0) {
-            str.add("tracing");
+        if ((flags & SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION) != 0) {
+            str.add("disable_gesture_split_invocation");
         }
         if ((flags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0) {
             str.add("asst_gesture_constrain");
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 87a90b5..91fb688 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -35,14 +35,12 @@
 
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
-import android.app.admin.flags.Flags;
 import android.content.Intent;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.media.AudioManager;
 import android.metrics.LogMaker;
-import android.os.Looper;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
@@ -98,15 +96,12 @@
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
 
-import com.google.common.util.concurrent.ListenableFuture;
-
 import dagger.Lazy;
 
 import kotlinx.coroutines.Job;
 
 import java.io.File;
 import java.util.Arrays;
-import java.util.concurrent.ExecutionException;
 
 import javax.inject.Inject;
 import javax.inject.Provider;
@@ -139,7 +134,6 @@
     private final BouncerMessageInteractor mBouncerMessageInteractor;
     private int mTranslationY;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
-    private final DevicePolicyManager mDevicePolicyManager;
     // Whether the volume keys should be handled by keyguard. If true, then
     // they will be handled here for specific media types such as music, otherwise
     // the audio service will bring up the volume dialog.
@@ -466,7 +460,6 @@
             SelectedUserInteractor selectedUserInteractor,
             DeviceProvisionedController deviceProvisionedController,
             FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
-            DevicePolicyManager devicePolicyManager,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
             Provider<DeviceEntryInteractor> deviceEntryInteractor
@@ -502,7 +495,6 @@
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mDeviceProvisionedController = deviceProvisionedController;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
-        mDevicePolicyManager = devicePolicyManager;
     }
 
     @Override
@@ -1113,36 +1105,35 @@
 
         if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts);
 
+        final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
         final int failedAttemptsBeforeWipe =
-                mDevicePolicyManager.getMaximumFailedPasswordsForWipe(null, userId);
+                dpm.getMaximumFailedPasswordsForWipe(null, userId);
 
         final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0
                 ? (failedAttemptsBeforeWipe - failedAttempts)
                 : Integer.MAX_VALUE; // because DPM returns 0 if no restriction
         if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
-            // The user has installed a DevicePolicyManager that requests a
-            // user/profile to be wiped N attempts. Once we get below the grace period,
-            // we post this dialog every time as a clear warning until the deletion
-            // fires. Check which profile has the strictest policy for failed password
-            // attempts.
-            final int expiringUser =
-                    mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(userId);
-            ListenableFuture<Integer> getMainUserIdFuture =
-                    mSelectedUserInteractor.getMainUserIdAsync();
-            getMainUserIdFuture.addListener(() -> {
-                Looper.prepare();
-                Integer mainUser;
-                try {
-                    mainUser = getMainUserIdFuture.get();
-                } catch (InterruptedException | ExecutionException e) {
-                    // Nothing we can, keep using the system user as the primary
-                    // user.
-                    mainUser = null;
+            // The user has installed a DevicePolicyManager that requests a user/profile to be wiped
+            // N attempts. Once we get below the grace period, we post this dialog every time as a
+            // clear warning until the deletion fires.
+            // Check which profile has the strictest policy for failed password attempts
+            final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId);
+            int userType = USER_TYPE_PRIMARY;
+            if (expiringUser == userId) {
+                // TODO: http://b/23522538
+                if (expiringUser != UserHandle.USER_SYSTEM) {
+                    userType = USER_TYPE_SECONDARY_USER;
                 }
-                showMessageForFailedUnlockAttempt(
-                        userId, expiringUser, mainUser, remainingBeforeWipe, failedAttempts);
-                Looper.loop();
-            }, ThreadUtils.getBackgroundExecutor());
+            } else if (expiringUser != UserHandle.USER_NULL) {
+                userType = USER_TYPE_WORK_PROFILE;
+            } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY
+            if (remainingBeforeWipe > 0) {
+                mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType);
+            } else {
+                // Too many attempts. The device will be wiped shortly.
+                Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!");
+                mView.showWipeDialog(failedAttempts, userType);
+            }
         }
         mLockPatternUtils.reportFailedPasswordAttempt(userId);
         if (timeoutMs > 0) {
@@ -1154,35 +1145,6 @@
         }
     }
 
-    @VisibleForTesting
-    void showMessageForFailedUnlockAttempt(int userId, int expiringUserId, Integer mainUserId,
-            int remainingBeforeWipe, int failedAttempts) {
-        int userType = USER_TYPE_PRIMARY;
-        if (expiringUserId == userId) {
-            int primaryUser = UserHandle.USER_SYSTEM;
-            if (Flags.headlessSingleUserFixes()) {
-                if (mainUserId != null) {
-                    primaryUser = mainUserId;
-                }
-            }
-            // TODO: http://b/23522538
-            if (expiringUserId != primaryUser) {
-                userType = USER_TYPE_SECONDARY_USER;
-            }
-        } else if (expiringUserId != UserHandle.USER_NULL) {
-            userType = USER_TYPE_WORK_PROFILE;
-        } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY
-        if (remainingBeforeWipe > 0) {
-            mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe,
-                    userType);
-        } else {
-            // Too many attempts. The device will be wiped shortly.
-            Slog.i(TAG, "Too many unlock attempts; user " + expiringUserId
-                    + " will be wiped!");
-            mView.showWipeDialog(failedAttempts, userType);
-        }
-    }
-
     private void getCurrentSecurityController(
             KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback) {
         mSecurityViewFlipperController
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
index 35f9344..004d5db 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.accessibility.data.repository.ColorCorrectionRepositoryImpl
 import com.android.systemui.accessibility.data.repository.ColorInversionRepository
 import com.android.systemui.accessibility.data.repository.ColorInversionRepositoryImpl
+import com.android.systemui.accessibility.data.repository.OneHandedModeRepository
+import com.android.systemui.accessibility.data.repository.OneHandedModeRepositoryImpl
 import com.android.systemui.accessibility.qs.QSAccessibilityModule
 import dagger.Binds
 import dagger.Module
@@ -34,6 +36,8 @@
     @Binds
     fun colorInversionRepository(impl: ColorInversionRepositoryImpl): ColorInversionRepository
 
+    @Binds fun oneHandedModeRepository(impl: OneHandedModeRepositoryImpl): OneHandedModeRepository
+
     @Binds
     fun accessibilityQsShortcutsRepository(
         impl: AccessibilityQsShortcutsRepositoryImpl
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepository.kt
new file mode 100644
index 0000000..d921025
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepository.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.accessibility.data.repository
+
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/** Provides data related to one handed mode. */
+interface OneHandedModeRepository {
+    /** Observable for whether one handed mode is enabled */
+    fun isEnabled(userHandle: UserHandle): Flow<Boolean>
+
+    /** Sets one handed mode enabled state. */
+    suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean
+}
+
+@SysUISingleton
+class OneHandedModeRepositoryImpl
+@Inject
+constructor(
+    @Background private val bgCoroutineContext: CoroutineContext,
+    @Application private val scope: CoroutineScope,
+    private val secureSettings: SecureSettings,
+) : OneHandedModeRepository {
+
+    private val userMap = mutableMapOf<Int, Flow<Boolean>>()
+
+    override fun isEnabled(userHandle: UserHandle): Flow<Boolean> =
+        userMap.getOrPut(userHandle.identifier) {
+            secureSettings
+                .observerFlow(userHandle.identifier, SETTING_NAME)
+                .onStart { emit(Unit) }
+                .map {
+                    secureSettings.getIntForUser(SETTING_NAME, DISABLED, userHandle.identifier) ==
+                        ENABLED
+                }
+                .distinctUntilChanged()
+                .flowOn(bgCoroutineContext)
+                .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_VALUE)
+        }
+
+    override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean =
+        withContext(bgCoroutineContext) {
+            secureSettings.putIntForUser(
+                SETTING_NAME,
+                if (isEnabled) ENABLED else DISABLED,
+                userHandle.identifier
+            )
+        }
+
+    companion object {
+        private const val SETTING_NAME = Settings.Secure.ONE_HANDED_MODE_ENABLED
+        private const val DISABLED = 0
+        private const val ENABLED = 1
+        private const val DEFAULT_VALUE = false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 99be762..54dd6d0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -41,6 +41,10 @@
 import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
 import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
 import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
+import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileDataInteractor
+import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.android.systemui.qs.tiles.impl.onehanded.ui.OneHandedModeTileMapper
 import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileDataInteractor
 import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
@@ -256,5 +260,24 @@
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
             )
+
+        /** Inject One Handed Mode Tile into tileViewModelMap in QSModule. */
+        @Provides
+        @IntoMap
+        @StringKey(ONE_HANDED_TILE_SPEC)
+        fun provideOneHandedModeTileViewModel(
+            factory: QSTileViewModelFactory.Static<OneHandedModeTileModel>,
+            mapper: OneHandedModeTileMapper,
+            stateInteractor: OneHandedModeTileDataInteractor,
+            userActionInteractor: OneHandedModeTileUserActionInteractor
+        ): QSTileViewModel =
+            if (Flags.qsNewTilesFuture())
+                factory.create(
+                    TileSpec.create(ONE_HANDED_TILE_SPEC),
+                    userActionInteractor,
+                    stateInteractor,
+                    mapper,
+                )
+            else StubQSTileViewModel
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index fcba425..5df7fc9 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.authentication.domain.interactor
 
-import android.app.admin.flags.Flags
 import android.os.UserHandle
 import com.android.internal.widget.LockPatternUtils
 import com.android.internal.widget.LockPatternView
@@ -289,15 +288,9 @@
     private suspend fun getWipeTarget(): WipeTarget {
         // Check which profile has the strictest policy for failed authentication attempts.
         val userToBeWiped = repository.getProfileWithMinFailedUnlockAttemptsForWipe()
-        val primaryUser =
-            if (Flags.headlessSingleUserFixes()) {
-                selectedUserInteractor.getMainUserId() ?: UserHandle.USER_SYSTEM
-            } else {
-                UserHandle.USER_SYSTEM
-            }
         return when (userToBeWiped) {
             selectedUserInteractor.getSelectedUserId() ->
-                if (userToBeWiped == primaryUser) {
+                if (userToBeWiped == UserHandle.USER_SYSTEM) {
                     WipeTarget.WholeDevice
                 } else {
                     WipeTarget.User
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index a8e9041..0f63f65 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToAodTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.OffToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToAodTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToDozingTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
@@ -196,6 +197,12 @@
 
     @Binds
     @IntoSet
+    abstract fun offToLockscreen(
+        impl: OffToLockscreenTransitionViewModel
+    ): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
     abstract fun primaryBouncerToAod(
         impl: PrimaryBouncerToAodTransitionViewModel
     ): DeviceEntryIconTransition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 40be73e..da2fcc4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -84,19 +84,21 @@
             .map { it.deviceEntryParentViewAlpha }
             .merge()
             .shareIn(scope, SharingStarted.WhileSubscribed())
+            .onStart { emit(initialAlphaFromKeyguardState(transitionInteractor.getCurrentState())) }
     private val alphaMultiplierFromShadeExpansion: Flow<Float> =
         combine(
-            showingAlternateBouncer,
-            shadeExpansion,
-            qsProgress,
-        ) { showingAltBouncer, shadeExpansion, qsProgress ->
-            val interpolatedQsProgress = (qsProgress * 2).coerceIn(0f, 1f)
-            if (showingAltBouncer) {
-                1f
-            } else {
-                (1f - shadeExpansion) * (1f - interpolatedQsProgress)
+                showingAlternateBouncer,
+                shadeExpansion,
+                qsProgress,
+            ) { showingAltBouncer, shadeExpansion, qsProgress ->
+                val interpolatedQsProgress = (qsProgress * 2).coerceIn(0f, 1f)
+                if (showingAltBouncer) {
+                    1f
+                } else {
+                    (1f - shadeExpansion) * (1f - interpolatedQsProgress)
+                }
             }
-        }
+            .onStart { emit(1f) }
     // Burn-in offsets in AOD
     private val nonAnimatedBurnInOffsets: Flow<BurnInOffsets> =
         combine(
@@ -122,14 +124,34 @@
             )
         }
 
-    val deviceEntryViewAlpha: StateFlow<Float> =
+    val deviceEntryViewAlpha: Flow<Float> =
         combine(
                 transitionAlpha,
                 alphaMultiplierFromShadeExpansion,
             ) { alpha, alphaMultiplier ->
                 alpha * alphaMultiplier
             }
-            .stateIn(scope = scope, started = SharingStarted.WhileSubscribed(), initialValue = 0f)
+            .stateIn(
+                scope = scope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = 0f,
+            )
+
+    private fun initialAlphaFromKeyguardState(keyguardState: KeyguardState): Float {
+        return when (keyguardState) {
+            KeyguardState.OFF,
+            KeyguardState.PRIMARY_BOUNCER,
+            KeyguardState.DOZING,
+            KeyguardState.DREAMING,
+            KeyguardState.GLANCEABLE_HUB,
+            KeyguardState.GONE,
+            KeyguardState.OCCLUDED,
+            KeyguardState.DREAMING_LOCKSCREEN_HOSTED, -> 0f
+            KeyguardState.AOD,
+            KeyguardState.ALTERNATE_BOUNCER,
+            KeyguardState.LOCKSCREEN -> 1f
+        }
+    }
     val useBackgroundProtection: StateFlow<Boolean> = isUdfpsSupported
     val burnInOffsets: Flow<BurnInOffsets> =
         deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
index 74094be..cf6a533 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardState
 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.flow.Flow
@@ -28,7 +29,7 @@
 @Inject
 constructor(
     animationFlow: KeyguardTransitionAnimationFlow,
-) {
+) : DeviceEntryIconTransition {
 
     private val transitionAnimation =
         animationFlow.setup(
@@ -43,4 +44,7 @@
             onStep = { it },
             onCancel = { 0f },
         )
+
+    override val deviceEntryParentViewAlpha: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(1f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileDataInteractor.kt
new file mode 100644
index 0000000..8c0fd2c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileDataInteractor.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.qs.tiles.impl.onehanded.domain
+
+import android.os.UserHandle
+import com.android.systemui.accessibility.data.repository.OneHandedModeRepository
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.android.wm.shell.onehanded.OneHanded
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Observes one handed mode state changes providing the [OneHandedModeTileModel]. */
+class OneHandedModeTileDataInteractor
+@Inject
+constructor(
+    private val oneHandedModeRepository: OneHandedModeRepository,
+) : QSTileDataInteractor<OneHandedModeTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<OneHandedModeTileModel> {
+        return oneHandedModeRepository.isEnabled(user).map { OneHandedModeTileModel(it) }
+    }
+    override fun availability(user: UserHandle): Flow<Boolean> =
+        flowOf(OneHanded.sIsSupportOneHandedMode)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileUserActionInteractor.kt
new file mode 100644
index 0000000..5cb0e18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileUserActionInteractor.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.qs.tiles.impl.onehanded.domain
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.accessibility.data.repository.OneHandedModeRepository
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles one handed mode tile clicks. */
+class OneHandedModeTileUserActionInteractor
+@Inject
+constructor(
+    private val oneHandedModeRepository: OneHandedModeRepository,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<OneHandedModeTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<OneHandedModeTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    oneHandedModeRepository.setIsEnabled(
+                        !data.isEnabled,
+                        user,
+                    )
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.expandable,
+                        Intent(Settings.ACTION_ONE_HANDED_SETTINGS)
+                    )
+                }
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/model/OneHandedModeTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/model/OneHandedModeTileModel.kt
new file mode 100644
index 0000000..7cebdfe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/model/OneHandedModeTileModel.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.qs.tiles.impl.onehanded.domain.model
+
+/**
+ * One handed mode tile model.
+ *
+ * @param isEnabled is true when one handed mode is enabled;
+ */
+@JvmInline value class OneHandedModeTileModel(val isEnabled: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
new file mode 100644
index 0000000..9166ed8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.qs.tiles.impl.onehanded.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [OneHandedModeTileModel] to [QSTileState]. */
+class OneHandedModeTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<OneHandedModeTileModel> {
+
+    override fun map(config: QSTileConfig, data: OneHandedModeTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            val subtitleArray = resources.getStringArray(R.array.tile_states_onehanded)
+            label = resources.getString(R.string.quick_settings_onehanded_label)
+            icon = {
+                Icon.Loaded(
+                    resources.getDrawable(
+                        com.android.internal.R.drawable.ic_qs_one_handed_mode,
+                        theme
+                    ),
+                    null
+                )
+            }
+            if (data.isEnabled) {
+                activationState = QSTileState.ActivationState.ACTIVE
+                secondaryLabel = subtitleArray[2]
+            } else {
+                activationState = QSTileState.ActivationState.INACTIVE
+                secondaryLabel = subtitleArray[1]
+            }
+            sideViewIcon = QSTileState.SideViewIcon.None
+            contentDescription = label
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 0673dcd..76bd80f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -37,7 +37,6 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_TRANSITION;
 
@@ -118,8 +117,6 @@
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInterface;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -131,6 +128,8 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
+import dagger.Lazy;
+
 /**
  * Class to send information from overview to launcher with a binder.
  */
@@ -701,8 +700,7 @@
             // Listen for tracing state changes
             @Override
             public void onTracingStateChanged(boolean enabled) {
-                mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled)
-                        .commitUpdate(mContext.getDisplayId());
+                // TODO(b/286509643) Cleanup callers of this; Unused downstream
             }
 
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
index 07e143a..ef1d87d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
@@ -87,7 +87,8 @@
                 AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_edit),
                 context.resources.getString(R.string.screenshot_edit_label),
                 context.resources.getString(R.string.screenshot_edit_description),
-            )
+            ),
+            showDuringEntrance = true,
         ) {
             debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" }
             uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString)
@@ -105,7 +106,8 @@
                 AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_share),
                 context.resources.getString(R.string.screenshot_share_label),
                 context.resources.getString(R.string.screenshot_share_description),
-            )
+            ),
+            showDuringEntrance = true,
         ) {
             debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" }
             uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString)
@@ -125,7 +127,8 @@
                 AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_scroll),
                 context.resources.getString(R.string.screenshot_scroll_label),
                 context.resources.getString(R.string.screenshot_scroll_label),
-            )
+            ),
+            showDuringEntrance = true,
         ) {
             onClick.run()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
index 9b5e7182..412b089 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.screenshot.ui.ScreenshotAnimationController
 import com.android.systemui.screenshot.ui.ScreenshotShelfView
 import com.android.systemui.screenshot.ui.binder.ScreenshotShelfViewBinder
+import com.android.systemui.screenshot.ui.viewmodel.AnimationState
 import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -119,12 +120,19 @@
     override fun updateOrientation(insets: WindowInsets) {}
 
     override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator {
-        val entrance = animationController.getEntranceAnimation(screenRect, showFlash)
-        entrance.doOnStart { thumbnailObserver.onEntranceStarted() }
+        val entrance =
+            animationController.getEntranceAnimation(screenRect, showFlash) {
+                viewModel.setAnimationState(AnimationState.ENTRANCE_REVEAL)
+            }
+        entrance.doOnStart {
+            thumbnailObserver.onEntranceStarted()
+            viewModel.setAnimationState(AnimationState.ENTRANCE_STARTED)
+        }
         entrance.doOnEnd {
             // reset the timeout when animation finishes
             callbacks?.onUserInteraction()
             thumbnailObserver.onEntranceComplete()
+            viewModel.setAnimationState(AnimationState.ENTRANCE_COMPLETE)
         }
         return entrance
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
index da26830..06e88f4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
@@ -47,7 +47,11 @@
             view.requireViewById(R.id.screenshot_dismiss_button)
         )
 
-    fun getEntranceAnimation(bounds: Rect, showFlash: Boolean): Animator {
+    fun getEntranceAnimation(
+        bounds: Rect,
+        showFlash: Boolean,
+        onRevealMilestone: () -> Unit
+    ): Animator {
         val entranceAnimation = AnimatorSet()
 
         val previewAnimator = getPreviewAnimator(bounds)
@@ -70,7 +74,19 @@
             entranceAnimation.doOnStart { screenshotPreview.visibility = View.INVISIBLE }
         }
 
-        entranceAnimation.play(getActionsAnimator()).with(previewAnimator)
+        val actionsAnimator = getActionsAnimator()
+        entranceAnimation.play(actionsAnimator).with(previewAnimator)
+
+        // This isn't actually animating anything but is basically a timer for the first 200ms of
+        // the entrance animation. Using an animator here ensures that this is scaled if we change
+        // animator duration scales.
+        val revealMilestoneAnimator =
+            ValueAnimator.ofFloat(0f).apply {
+                duration = 0
+                startDelay = ACTION_REVEAL_DELAY_MS
+                doOnEnd { onRevealMilestone() }
+            }
+        entranceAnimation.play(revealMilestoneAnimator).with(actionsAnimator)
 
         val fadeInAnimator = ValueAnimator.ofFloat(0f, 1f)
         fadeInAnimator.addUpdateListener {
@@ -198,5 +214,6 @@
         private const val FLASH_OUT_DURATION_MS: Long = 217
         private const val PREVIEW_X_ANIMATION_DURATION_MS: Long = 234
         private const val PREVIEW_Y_ANIMATION_DURATION_MS: Long = 500
+        private const val ACTION_REVEAL_DELAY_MS: Long = 200
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index bc35e6b..43c0107 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -31,6 +31,8 @@
 import com.android.systemui.screenshot.ScreenshotEvent
 import com.android.systemui.screenshot.ui.ScreenshotShelfView
 import com.android.systemui.screenshot.ui.SwipeGestureListener
+import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
+import com.android.systemui.screenshot.ui.viewmodel.AnimationState
 import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
 import com.android.systemui.util.children
 import kotlinx.coroutines.Dispatchers
@@ -59,7 +61,6 @@
         val previewBorder = view.requireViewById<View>(R.id.screenshot_preview_border)
         previewView.clipToOutline = true
         previewViewBlur.clipToOutline = true
-        val actionsContainer: LinearLayout = view.requireViewById(R.id.screenshot_actions)
         val dismissButton = view.requireViewById<View>(R.id.screenshot_dismiss_button)
         dismissButton.visibility = if (viewModel.showDismissButton) View.VISIBLE else View.GONE
         dismissButton.setOnClickListener {
@@ -90,44 +91,22 @@
                     }
                     launch {
                         viewModel.actions.collect { actions ->
-                            val visibleActions = actions.filter { it.visible }
-
-                            if (visibleActions.isNotEmpty()) {
-                                view
-                                    .requireViewById<View>(R.id.actions_container_background)
-                                    .visibility = View.VISIBLE
-                            }
-
-                            // Remove any buttons not in the new list, then do another pass to add
-                            // any new actions and update any that are already there.
-                            // This assumes that actions can never change order and that each action
-                            // ID is unique.
-                            val newIds = visibleActions.map { it.id }
-
-                            for (child in actionsContainer.children.toList()) {
-                                if (child.tag !in newIds) {
-                                    actionsContainer.removeView(child)
-                                }
-                            }
-
-                            for ((index, action) in visibleActions.withIndex()) {
-                                val currentView: View? = actionsContainer.getChildAt(index)
-                                if (action.id == currentView?.tag) {
-                                    // Same ID, update the display
-                                    ActionButtonViewBinder.bind(currentView, action)
-                                } else {
-                                    // Different ID. Removals have already happened so this must
-                                    // mean that the new action must be inserted here.
-                                    val actionButton =
-                                        layoutInflater.inflate(
-                                            R.layout.shelf_action_chip,
-                                            actionsContainer,
-                                            false
-                                        )
-                                    actionsContainer.addView(actionButton, index)
-                                    ActionButtonViewBinder.bind(actionButton, action)
-                                }
-                            }
+                            updateActions(
+                                actions,
+                                viewModel.animationState.value,
+                                view,
+                                layoutInflater
+                            )
+                        }
+                    }
+                    launch {
+                        viewModel.animationState.collect { animationState ->
+                            updateActions(
+                                viewModel.actions.value,
+                                animationState,
+                                view,
+                                layoutInflater
+                            )
                         }
                     }
                 }
@@ -135,6 +114,53 @@
         }
     }
 
+    private fun updateActions(
+        actions: List<ActionButtonViewModel>,
+        animationState: AnimationState,
+        view: ScreenshotShelfView,
+        layoutInflater: LayoutInflater
+    ) {
+        val actionsContainer: LinearLayout = view.requireViewById(R.id.screenshot_actions)
+        val visibleActions =
+            actions.filter {
+                it.visible &&
+                    (animationState == AnimationState.ENTRANCE_COMPLETE ||
+                        animationState == AnimationState.ENTRANCE_REVEAL ||
+                        it.showDuringEntrance)
+            }
+
+        if (visibleActions.isNotEmpty()) {
+            view.requireViewById<View>(R.id.actions_container_background).visibility = View.VISIBLE
+        }
+
+        // Remove any buttons not in the new list, then do another pass to add
+        // any new actions and update any that are already there.
+        // This assumes that actions can never change order and that each action
+        // ID is unique.
+        val newIds = visibleActions.map { it.id }
+
+        for (child in actionsContainer.children.toList()) {
+            if (child.tag !in newIds) {
+                actionsContainer.removeView(child)
+            }
+        }
+
+        for ((index, action) in visibleActions.withIndex()) {
+            val currentView: View? = actionsContainer.getChildAt(index)
+            if (action.id == currentView?.tag) {
+                // Same ID, update the display
+                ActionButtonViewBinder.bind(currentView, action)
+            } else {
+                // Different ID. Removals have already happened so this must
+                // mean that the new action must be inserted here.
+                val actionButton =
+                    layoutInflater.inflate(R.layout.shelf_action_chip, actionsContainer, false)
+                actionsContainer.addView(actionButton, index)
+                ActionButtonViewBinder.bind(actionButton, action)
+            }
+        }
+    }
+
     private fun setScreenshotBitmap(screenshotPreview: ImageView, bitmap: Bitmap) {
         screenshotPreview.setImageBitmap(bitmap)
         val hasPortraitAspectRatio = bitmap.width < bitmap.height
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
index c5fa8db..364ab76 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
@@ -20,6 +20,7 @@
     val appearance: ActionButtonAppearance,
     val id: Int,
     val visible: Boolean,
+    val showDuringEntrance: Boolean,
     val onClicked: (() -> Unit)?,
 ) {
     companion object {
@@ -29,7 +30,14 @@
 
         fun withNextId(
             appearance: ActionButtonAppearance,
+            showDuringEntrance: Boolean,
             onClicked: (() -> Unit)?
-        ): ActionButtonViewModel = ActionButtonViewModel(appearance, getId(), true, onClicked)
+        ): ActionButtonViewModel =
+            ActionButtonViewModel(appearance, getId(), true, showDuringEntrance, onClicked)
+
+        fun withNextId(
+            appearance: ActionButtonAppearance,
+            onClicked: (() -> Unit)?
+        ): ActionButtonViewModel = withNextId(appearance, showDuringEntrance = true, onClicked)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
index f67ad40..5f36f73 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
@@ -29,6 +29,9 @@
     val previewAction: StateFlow<(() -> Unit)?> = _previewAction
     private val _actions = MutableStateFlow(emptyList<ActionButtonViewModel>())
     val actions: StateFlow<List<ActionButtonViewModel>> = _actions
+    private val _animationState = MutableStateFlow(AnimationState.NOT_STARTED)
+    val animationState: StateFlow<AnimationState> = _animationState
+
     val showDismissButton: Boolean
         get() = accessibilityManager.isEnabled
 
@@ -40,9 +43,14 @@
         _previewAction.value = onClick
     }
 
-    fun addAction(actionAppearance: ActionButtonAppearance, onClicked: (() -> Unit)): Int {
+    fun addAction(
+        actionAppearance: ActionButtonAppearance,
+        showDuringEntrance: Boolean,
+        onClicked: (() -> Unit)
+    ): Int {
         val actionList = _actions.value.toMutableList()
-        val action = ActionButtonViewModel.withNextId(actionAppearance, onClicked)
+        val action =
+            ActionButtonViewModel.withNextId(actionAppearance, showDuringEntrance, onClicked)
         actionList.add(action)
         _actions.value = actionList
         return action.id
@@ -57,6 +65,7 @@
                     actionList[index].appearance,
                     actionId,
                     visible,
+                    actionList[index].showDuringEntrance,
                     actionList[index].onClicked
                 )
             _actions.value = actionList
@@ -74,6 +83,7 @@
                     appearance,
                     actionId,
                     actionList[index].visible,
+                    actionList[index].showDuringEntrance,
                     actionList[index].onClicked
                 )
             _actions.value = actionList
@@ -92,13 +102,26 @@
         }
     }
 
+    // TODO: this should be handled entirely within the view binder.
+    fun setAnimationState(state: AnimationState) {
+        _animationState.value = state
+    }
+
     fun reset() {
         _preview.value = null
         _previewAction.value = null
         _actions.value = listOf()
+        _animationState.value = AnimationState.NOT_STARTED
     }
 
     companion object {
         const val TAG = "ScreenshotViewModel"
     }
 }
+
+enum class AnimationState {
+    NOT_STARTED,
+    ENTRANCE_STARTED, // The first 200ms of the entrance animation
+    ENTRANCE_REVEAL, // The rest of the entrance animation
+    ENTRANCE_COMPLETE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt b/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt
index 384acc4..dd79425 100644
--- a/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt
@@ -28,6 +28,9 @@
  * Returns updating [Slice] for a [sliceUri]. It's null when there is no slice available for the
  * provided Uri. This can change overtime because of external changes (like device being
  * connected/disconnected).
+ *
+ * The flow should be [kotlinx.coroutines.flow.flowOn] the main thread because [SliceViewManager]
+ * isn't thread-safe. An exception will be thrown otherwise.
  */
 fun SliceViewManager.sliceForUri(sliceUri: Uri): Flow<Slice?> =
     ConflatedCallbackFlow.conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index e980794..d0cebae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -169,6 +169,14 @@
                 }
             }
 
+            // On the final call to {@link #resetViewState}, the alpha is set back to 1f but
+            // ambientState.isExpansionChanging() is now false. This causes a flicker on the
+            // EmptyShadeView after the shade is collapsed. Make sure the empty shade view
+            // isn't visible unless the shade is expanded.
+            if (view instanceof EmptyShadeView && ambientState.getExpansionFraction() == 0f) {
+                viewState.setAlpha(0f);
+            }
+
             // For EmptyShadeView if on keyguard, we need to control the alpha to create
             // a nice transition when the user is dragging down the notification panel.
             if (view instanceof EmptyShadeView && ambientState.isOnKeyguard()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index dea9416..2e1ab38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -59,7 +59,10 @@
     }
 
     override fun notifyThemeChanged() {
-        val listeners = ArrayList(listeners)
+        // Avoid concurrent modification exception
+        val listeners = synchronized(this.listeners) {
+           ArrayList(this.listeners)
+        }
 
         listeners.filterForEach({ this.listeners.contains(it) }) {
             it.onThemeChanged()
@@ -68,8 +71,9 @@
 
     override fun onConfigurationChanged(newConfig: Configuration) {
         // Avoid concurrent modification exception
-        val listeners = ArrayList(listeners)
-
+        val listeners = synchronized(this.listeners) {
+           ArrayList(this.listeners)
+        }
         listeners.filterForEach({ this.listeners.contains(it) }) {
             it.onConfigChanged(newConfig)
         }
@@ -148,12 +152,16 @@
     }
 
     override fun addCallback(listener: ConfigurationListener) {
-        listeners.add(listener)
+        synchronized(listeners) {
+            listeners.add(listener)
+        }
         listener.onDensityOrFontScaleChanged()
     }
 
     override fun removeCallback(listener: ConfigurationListener) {
-        listeners.remove(listener)
+        synchronized(listeners) {
+            listeners.remove(listener)
+        }
     }
 
     override fun isLayoutRtl(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 0739b836..a7c4187 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -318,8 +318,8 @@
         }
 
     companion object {
-        // TTL for satellite polling is one hour
-        const val POLLING_INTERVAL_MS: Long = 1000 * 60 * 60
+        // TTL for satellite polling is twenty minutes
+        const val POLLING_INTERVAL_MS: Long = 1000 * 60 * 20
 
         // Let the system boot up and stabilize before we check for system support
         const val MIN_UPTIME: Long = 1000 * 60
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index a817b31..37be1c6 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -18,7 +18,6 @@
 package com.android.systemui.user.data.repository
 
 import android.annotation.SuppressLint
-import android.annotation.UserIdInt
 import android.content.Context
 import android.content.pm.UserInfo
 import android.os.UserHandle
@@ -108,22 +107,6 @@
     fun isSimpleUserSwitcher(): Boolean
 
     fun isUserSwitcherEnabled(): Boolean
-
-    /**
-     * Returns the user ID of the "main user" of the device. This user may have access to certain
-     * features which are limited to at most one user. There will never be more than one main user
-     * on a device.
-     *
-     * <p>Currently, on most form factors the first human user on the device will be the main user;
-     * in the future, the concept may be transferable, so a different user (or even no user at all)
-     * may be designated the main user instead. On other form factors there might not be a main
-     * user.
-     *
-     * <p> When the device doesn't have a main user, this will return {@code null}.
-     *
-     * @see [UserManager.getMainUser]
-     */
-    @UserIdInt suspend fun getMainUserId(): Int?
 }
 
 @SysUISingleton
@@ -256,10 +239,6 @@
         return _userSwitcherSettings.value.isUserSwitcherEnabled
     }
 
-    override suspend fun getMainUserId(): Int? {
-        return withContext(backgroundDispatcher) { manager.mainUser?.identifier }
-    }
-
     private suspend fun getSettings(): UserSwitcherSettingsModel {
         return withContext(backgroundDispatcher) {
             val isSimpleUserSwitcher =
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
index a5728d0..38b381a 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
@@ -2,27 +2,17 @@
 
 import android.annotation.UserIdInt
 import android.content.pm.UserInfo
-import android.os.UserManager
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.Flags.refactorGetCurrentUser
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.user.data.repository.UserRepository
-import com.google.common.util.concurrent.ListenableFuture
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.guava.future
 
 /** Encapsulates business logic to interact the selected user */
 @SysUISingleton
-class SelectedUserInteractor
-@Inject
-constructor(
-    @Application private val applicationScope: CoroutineScope,
-    private val repository: UserRepository
-) {
+class SelectedUserInteractor @Inject constructor(private val repository: UserRepository) {
 
     /** Flow providing the ID of the currently selected user. */
     val selectedUser = repository.selectedUserInfo.map { it.id }.distinctUntilChanged()
@@ -48,41 +38,4 @@
             KeyguardUpdateMonitor.getCurrentUser()
         }
     }
-
-    /**
-     * Returns the user ID of the "main user" of the device. This user may have access to certain
-     * features which are limited to at most one user. There will never be more than one main user
-     * on a device.
-     *
-     * <p>Currently, on most form factors the first human user on the device will be the main user;
-     * in the future, the concept may be transferable, so a different user (or even no user at all)
-     * may be designated the main user instead. On other form factors there might not be a main
-     * user.
-     *
-     * <p> When the device doesn't have a main user, this will return {@code null}.
-     *
-     * @see [UserManager.getMainUser]
-     */
-    @UserIdInt
-    suspend fun getMainUserId(): Int? {
-        return repository.getMainUserId()
-    }
-
-    /**
-     * Returns a [ListenableFuture] for the user ID of the "main user" of the device. This user may
-     * have access to certain features which are limited to at most one user. There will never be
-     * more than one main user on a device.
-     *
-     * <p>Currently, on most form factors the first human user on the device will be the main user;
-     * in the future, the concept may be transferable, so a different user (or even no user at all)
-     * may be designated the main user instead. On other form factors there might not be a main
-     * user.
-     *
-     * <p> When the device doesn't have a main user, this will return {@code null}.
-     *
-     * @see [UserManager.getMainUser]
-     */
-    fun getMainUserIdAsync(): ListenableFuture<Int?> {
-        return applicationScope.future { getMainUserId() }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
index 8ce3b1f..3117abc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
@@ -23,6 +23,7 @@
 import com.android.settingslib.bluetooth.BluetoothUtils
 import com.android.settingslib.media.BluetoothMediaDevice
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.slice.sliceForUri
 import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
 import dagger.assisted.Assisted
@@ -57,6 +58,7 @@
 constructor(
     mediaRepositoryFactory: LocalMediaRepositoryFactory,
     @Background private val backgroundCoroutineContext: CoroutineContext,
+    @Main private val mainCoroutineContext: CoroutineContext,
     @Assisted private val sliceViewManager: SliceViewManager,
 ) : AncSliceRepository {
 
@@ -73,7 +75,7 @@
             .distinctUntilChanged()
             .flatMapLatest { sliceUri ->
                 sliceUri ?: return@flatMapLatest flowOf(null)
-                sliceViewManager.sliceForUri(sliceUri)
+                sliceViewManager.sliceForUri(sliceUri).flowOn(mainCoroutineContext)
             }
             .flowOn(backgroundCoroutineContext)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
index bee79bb..c980eb4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.volume.panel.component.anc.ui.viewmodel
 
+import android.content.Intent
 import androidx.slice.Slice
+import androidx.slice.SliceItem
 import com.android.systemui.volume.panel.component.anc.domain.AncAvailabilityCriteria
 import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor
 import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices
@@ -59,6 +61,31 @@
             .map { it.buttonSlice }
             .stateIn(coroutineScope, SharingStarted.Eagerly, null)
 
+    fun isClickable(slice: Slice?): Boolean {
+        slice ?: return false
+        val slices = ArrayDeque<SliceItem>()
+        slices.addAll(slice.items)
+        while (slices.isNotEmpty()) {
+            val item: SliceItem = slices.removeFirst()
+            when (item.format) {
+                android.app.slice.SliceItem.FORMAT_ACTION -> {
+                    val itemActionIntent: Intent? = item.action?.intent
+                    if (itemActionIntent?.hasExtra(EXTRA_ANC_ENABLED) == true) {
+                        return itemActionIntent.getBooleanExtra(EXTRA_ANC_ENABLED, true)
+                    }
+                }
+                android.app.slice.SliceItem.FORMAT_SLICE -> {
+                    item.slice?.items?.let(slices::addAll)
+                }
+            }
+        }
+        return true
+    }
+
+    private companion object {
+        const val EXTRA_ANC_ENABLED = "EXTRA_ANC_ENABLED"
+    }
+
     /** Call this to update [popupSlice] width in a reaction to container size change. */
     fun onPopupSliceWidthChanged(width: Int) {
         interactor.onPopupSliceWidthChanged(width)
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 263ddc1..b86a7c9 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -273,6 +274,13 @@
                 splitScreen.setSplitscreenFocus(leftOrTop);
             }
         });
+        splitScreen.registerSplitAnimationListener(new SplitScreen.SplitInvocationListener() {
+            @Override
+            public void onSplitAnimationInvoked(boolean animationRunning) {
+                mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION, animationRunning)
+                        .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+            }
+        }, mSysUiMainExecutor);
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index 67ca9a4..0231486 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -16,80 +16,73 @@
 
 package com.android.systemui.biometrics
 
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.user.domain.UserDomainLayerModule
-import dagger.BindsInstance
-import dagger.Component
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shadeTestUtil
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.verifyZeroInteractions
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class AuthDialogPanelInteractionDetectorTest(flags: FlagsParameterization?) : SysuiTestCase() {
 
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                UserDomainLayerModule::class,
-                BiometricsDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<AuthDialogPanelInteractionDetector> {
-
-        val shadeRepository: FakeShadeRepository
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                featureFlags: FakeFeatureFlagsClassicModule,
-            ): TestComponent
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
         }
     }
 
-    private val testComponent: TestComponent =
-        DaggerAuthDialogPanelInteractionDetectorTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
-            )
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags!!)
+    }
 
-    private val detector: AuthDialogPanelInteractionDetector = testComponent.underTest
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
 
     @Mock private lateinit var action: Runnable
 
+    lateinit var detector: AuthDialogPanelInteractionDetector
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        detector =
+            AuthDialogPanelInteractionDetector(
+                kosmos.applicationCoroutineScope,
+                { kosmos.shadeInteractor },
+            )
     }
 
     @Test
     fun enableDetector_expand_shouldRunAction() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN shade is closed and detector is enabled
-            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeTestUtil.setShadeExpansion(0f)
             detector.enable(action)
             runCurrent()
 
             // WHEN shade expands
-            shadeRepository.setLegacyShadeTracking(true)
-            shadeRepository.setLegacyShadeExpansion(.5f)
+            shadeTestUtil.setTracking(true)
+            shadeTestUtil.setShadeExpansion(.5f)
             runCurrent()
 
             // THEN action was run
@@ -98,9 +91,9 @@
 
     @Test
     fun enableDetector_isUserInteractingTrue_shouldNotPostRunnable() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN isInteracting starts true
-            shadeRepository.setLegacyShadeTracking(true)
+            shadeTestUtil.setTracking(true)
             runCurrent()
             detector.enable(action)
 
@@ -110,33 +103,34 @@
 
     @Test
     fun enableDetector_shadeExpandImmediate_shouldNotPostRunnable() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN shade is closed and detector is enabled
-            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeTestUtil.setShadeExpansion(0f)
             detector.enable(action)
             runCurrent()
 
             // WHEN shade expands fully instantly
-            shadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             runCurrent()
 
             // THEN action not run
             verifyZeroInteractions(action)
+            detector.disable()
         }
 
     @Test
     fun disableDetector_shouldNotPostRunnable() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN shade is closed and detector is enabled
-            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeTestUtil.setShadeExpansion(0f)
             detector.enable(action)
             runCurrent()
 
             // WHEN detector is disabled and shade opens
             detector.disable()
             runCurrent()
-            shadeRepository.setLegacyShadeTracking(true)
-            shadeRepository.setLegacyShadeExpansion(.5f)
+            shadeTestUtil.setTracking(true)
+            shadeTestUtil.setShadeExpansion(.5f)
             runCurrent()
 
             // THEN action not run
@@ -145,17 +139,18 @@
 
     @Test
     fun enableDetector_beginCollapse_shouldNotPostRunnable() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN shade is open and detector is enabled
-            shadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             detector.enable(action)
             runCurrent()
 
             // WHEN shade begins to collapse
-            shadeRepository.setLegacyShadeExpansion(.5f)
+            shadeTestUtil.programmaticCollapseShade()
             runCurrent()
 
             // THEN action not run
             verifyZeroInteractions(action)
+            detector.disable()
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
index d44e26c..e32086b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
@@ -34,20 +34,21 @@
 
         assertThat(viewModel.actions.value).isEmpty()
 
-        viewModel.addAction(appearance, onclick)
+        viewModel.addAction(appearance, true, onclick)
 
         assertThat(viewModel.actions.value).hasSize(1)
 
         val added = viewModel.actions.value[0]
         assertThat(added.appearance).isEqualTo(appearance)
         assertThat(added.onClicked).isEqualTo(onclick)
+        assertThat(added.showDuringEntrance).isTrue()
     }
 
     @Test
     fun testRemoveAction() {
         val viewModel = ScreenshotViewModel(accessibilityManager)
-        val firstId = viewModel.addAction(ActionButtonAppearance(null, "", ""), {})
-        val secondId = viewModel.addAction(appearance, onclick)
+        val firstId = viewModel.addAction(ActionButtonAppearance(null, "", ""), false, {})
+        val secondId = viewModel.addAction(appearance, false, onclick)
 
         assertThat(viewModel.actions.value).hasSize(2)
         assertThat(firstId).isNotEqualTo(secondId)
@@ -58,13 +59,14 @@
 
         val remaining = viewModel.actions.value[0]
         assertThat(remaining.appearance).isEqualTo(appearance)
+        assertThat(remaining.showDuringEntrance).isFalse()
         assertThat(remaining.onClicked).isEqualTo(onclick)
     }
 
     @Test
     fun testUpdateActionAppearance() {
         val viewModel = ScreenshotViewModel(accessibilityManager)
-        val id = viewModel.addAction(appearance, onclick)
+        val id = viewModel.addAction(appearance, false, onclick)
         val otherAppearance = ActionButtonAppearance(null, "Other", "Other")
 
         viewModel.updateActionAppearance(id, otherAppearance)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 158f38d..347620a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -19,12 +19,13 @@
 package com.android.systemui.statusbar.notification.footer.ui.viewmodel
 
 import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
-import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -33,7 +34,7 @@
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
@@ -45,13 +46,16 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 @SmallTest
 @EnableFlags(FooterViewRefactor.FLAG_NAME)
-class FooterViewModelTest : SysuiTestCase() {
+class FooterViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
@@ -59,11 +63,29 @@
     private val testScope = kosmos.testScope
     private val activeNotificationListRepository = kosmos.activeNotificationListRepository
     private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
-    private val shadeRepository = kosmos.shadeRepository
     private val powerRepository = kosmos.powerRepository
     private val fakeSecureSettingsRepository = kosmos.fakeSecureSettingsRepository
 
-    val underTest = kosmos.footerViewModel
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+    private lateinit var underTest: FooterViewModel
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags!!)
+    }
+
+    @Before
+    fun setup() {
+        underTest = kosmos.footerViewModel
+    }
 
     @Test
     fun messageVisible_whenFilteredNotifications() =
@@ -146,11 +168,9 @@
             val visible by collectLastValue(underTest.clearAllButton.isVisible)
             runCurrent()
 
-            // WHEN shade is expanded
+            // WHEN shade is expanded AND QS not expanded
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            shadeRepository.setLegacyShadeExpansion(1f)
-            // AND QS not expanded
-            shadeRepository.setQsExpansion(0f)
+            shadeTestUtil.setShadeAndQsExpansion(1f, 0f)
             // AND device is awake
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.AWAKE,
@@ -182,9 +202,9 @@
 
             // WHEN shade is collapsed
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeTestUtil.setShadeExpansion(0f)
             // AND QS not expanded
-            shadeRepository.setQsExpansion(0f)
+            shadeTestUtil.setQsExpansion(0f)
             // AND device is awake
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.AWAKE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 82725d6..a6fb718 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -394,8 +394,20 @@
     }
 
     @Test
+    fun resetViewStates_shadeCollapsed_emptyShadeViewBecomesTransparent() {
+        ambientState.expansionFraction = 0f
+        stackScrollAlgorithm.initView(context)
+        hostView.removeAllViews()
+        hostView.addView(emptyShadeView)
+
+        stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+        assertThat(emptyShadeView.viewState.alpha).isEqualTo(0f)
+    }
+
+    @Test
     fun resetViewStates_isOnKeyguard_emptyShadeViewBecomesOpaque() {
-        ambientState.setStatusBarState(StatusBarState.SHADE)
+        ambientState.setStatusBarState(StatusBarState.KEYGUARD)
         ambientState.fractionToShade = 0.25f
         stackScrollAlgorithm.initView(context)
         hostView.removeAllViews()
@@ -403,7 +415,8 @@
 
         stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
 
-        assertThat(emptyShadeView.viewState.alpha).isEqualTo(1f)
+        val expected = getContentAlpha(ambientState.fractionToShade)
+        assertThat(emptyShadeView.viewState.alpha).isEqualTo(expected)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
index 78d4f02..140e919 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
@@ -7,7 +7,6 @@
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineScope
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -24,7 +23,7 @@
     @Before
     fun setUp() {
         userRepository.setUserInfos(USER_INFOS)
-        underTest = SelectedUserInteractor(TestCoroutineScope(), userRepository)
+        underTest = SelectedUserInteractor(userRepository)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeOneHandedModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeOneHandedModeRepository.kt
new file mode 100644
index 0000000..ac135af
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeOneHandedModeRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.accessibility.data.repository
+
+import android.os.UserHandle
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeOneHandedModeRepository : OneHandedModeRepository {
+    private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>()
+
+    override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> {
+        return getFlow(userHandle.identifier)
+    }
+
+    override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean {
+        getFlow(userHandle.identifier).value = isEnabled
+        return true
+    }
+
+    /** initializes the flow if already not */
+    private fun getFlow(userId: Int): MutableStateFlow<Boolean> {
+        return userMap.getOrPut(userId) { MutableStateFlow(false) }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryKosmos.kt
new file mode 100644
index 0000000..9ee200a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.accessibility.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeOneHandedModeRepository by Kosmos.Fixture { FakeOneHandedModeRepository() }
+val Kosmos.oneHandedModeRepository by Kosmos.Fixture { fakeOneHandedModeRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index 2e2cf9a..0975687 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -81,7 +81,8 @@
                 com.android.systemui.Flags.constraintBp() &&
                 !Utils.isBiometricAllowed(promptInfo) &&
                 Utils.isDeviceCredentialAllowed(promptInfo) &&
-                promptInfo.contentView != null
+                promptInfo.contentView != null &&
+                !promptInfo.isContentViewMoreOptionsButtonUsed
         _showBpWithoutIconForCredential.value = showBpForCredential && !hasCredentialViewShown
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
index c7b06b6..7eef704 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
@@ -58,7 +58,7 @@
         bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
         keyguardUpdateMonitor: KeyguardUpdateMonitor = mock(KeyguardUpdateMonitor::class.java),
         powerRepository: FakePowerRepository = FakePowerRepository(),
-        userRepository: FakeUserRepository = FakeUserRepository()
+        userRepository: FakeUserRepository = FakeUserRepository(),
     ): WithDependencies {
         val primaryBouncerInteractor =
             PrimaryBouncerInteractor(
@@ -95,11 +95,7 @@
             PowerInteractorFactory.create(
                 repository = powerRepository,
             )
-        val selectedUserInteractor =
-            SelectedUserInteractor(
-                applicationScope = testScope.backgroundScope,
-                repository = userRepository
-            )
+        val selectedUserInteractor = SelectedUserInteractor(repository = userRepository)
         return WithDependencies(
             trustRepository = trustRepository,
             keyguardRepository = keyguardRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/onehanded/OneHandedModeTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/onehanded/OneHandedModeTileKosmos.kt
new file mode 100644
index 0000000..d9c0361
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/onehanded/OneHandedModeTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.qs.tiles.impl.onehanded
+
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsOneHandedModeTileConfig by
+    Kosmos.Fixture { QSAccessibilityModule.provideOneHandedTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
index 59a5bb5..38ede44 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
@@ -59,11 +59,23 @@
         delegate.setLockscreenShadeExpansion(lockscreenShadeExpansion)
     }
 
-    /** Sets whether the user is moving the shade with touch input. */
+    /** Sets whether the user is moving the shade with touch input on Lockscreen. */
     fun setLockscreenShadeTracking(lockscreenShadeTracking: Boolean) {
         delegate.assertFlagValid()
         delegate.setLockscreenShadeTracking(lockscreenShadeTracking)
     }
+
+    /** Sets whether the user is moving the shade with touch input. */
+    fun setTracking(tracking: Boolean) {
+        delegate.assertFlagValid()
+        delegate.setTracking(tracking)
+    }
+
+    /** Sets the shade to half collapsed with no touch input. */
+    fun programmaticCollapseShade() {
+        delegate.assertFlagValid()
+        delegate.programmaticCollapseShade()
+    }
 }
 
 /** Sets up shade state for tests for a specific value of the scene container flag. */
@@ -80,11 +92,17 @@
     /** Sets whether the user is moving the shade with touch input. */
     fun setLockscreenShadeTracking(lockscreenShadeTracking: Boolean)
 
+    /** Sets whether the user is moving the shade with touch input. */
+    fun setTracking(tracking: Boolean)
+
     /** Sets shade expansion to a value between 0-1. */
     fun setShadeExpansion(shadeExpansion: Float)
 
     /** Sets QS expansion to a value between 0-1. */
     fun setQsExpansion(qsExpansion: Float)
+
+    /** Sets the shade to half collapsed with no touch input. */
+    fun programmaticCollapseShade()
 }
 
 /** Sets up shade state for tests when the scene container flag is disabled. */
@@ -104,6 +122,10 @@
         shadeRepository.setLegacyLockscreenShadeTracking(lockscreenShadeTracking)
     }
 
+    override fun setTracking(tracking: Boolean) {
+        shadeRepository.setLegacyShadeTracking(tracking)
+    }
+
     override fun assertFlagValid() {
         Assert.assertFalse(SceneContainerFlag.isEnabled)
     }
@@ -119,6 +141,11 @@
         shadeRepository.setQsExpansion(qsExpansion)
         testScope.runCurrent()
     }
+
+    override fun programmaticCollapseShade() {
+        shadeRepository.setLegacyShadeExpansion(.5f)
+        testScope.runCurrent()
+    }
 }
 
 /** Sets up shade state for tests when the scene container flag is enabled. */
@@ -127,14 +154,16 @@
     val isUserInputOngoing = MutableStateFlow(true)
 
     override fun setShadeAndQsExpansion(shadeExpansion: Float, qsExpansion: Float) {
-        if (shadeExpansion == 0f) {
-            setTransitionProgress(Scenes.Lockscreen, Scenes.QuickSettings, qsExpansion)
-        } else if (qsExpansion == 0f) {
-            setTransitionProgress(Scenes.Lockscreen, Scenes.Shade, shadeExpansion)
-        } else if (shadeExpansion == 1f) {
+        if (shadeExpansion == 1f) {
             setIdleScene(Scenes.Shade)
         } else if (qsExpansion == 1f) {
             setIdleScene(Scenes.QuickSettings)
+        } else if (shadeExpansion == 0f && qsExpansion == 0f) {
+            setIdleScene(Scenes.Lockscreen)
+        } else if (shadeExpansion == 0f) {
+            setTransitionProgress(Scenes.Lockscreen, Scenes.QuickSettings, qsExpansion)
+        } else if (qsExpansion == 0f) {
+            setTransitionProgress(Scenes.Lockscreen, Scenes.Shade, shadeExpansion)
         } else {
             setTransitionProgress(Scenes.Shade, Scenes.QuickSettings, qsExpansion)
         }
@@ -150,6 +179,10 @@
         setShadeAndQsExpansion(0f, qsExpansion)
     }
 
+    override fun programmaticCollapseShade() {
+        setTransitionProgress(Scenes.Shade, Scenes.Lockscreen, .5f, false)
+    }
+
     override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) {
         if (lockscreenShadeExpansion == 0f) {
             setIdleScene(Scenes.Lockscreen)
@@ -161,7 +194,11 @@
     }
 
     override fun setLockscreenShadeTracking(lockscreenShadeTracking: Boolean) {
-        isUserInputOngoing.value = lockscreenShadeTracking
+        setTracking(lockscreenShadeTracking)
+    }
+
+    override fun setTracking(tracking: Boolean) {
+        isUserInputOngoing.value = tracking
     }
 
     private fun setIdleScene(scene: SceneKey) {
@@ -172,7 +209,12 @@
         testScope.runCurrent()
     }
 
-    private fun setTransitionProgress(from: SceneKey, to: SceneKey, progress: Float) {
+    private fun setTransitionProgress(
+        from: SceneKey,
+        to: SceneKey,
+        progress: Float,
+        isInitiatedByUserInput: Boolean = true
+    ) {
         sceneInteractor.changeScene(from, "test")
         val transitionState =
             MutableStateFlow<ObservableTransitionState>(
@@ -181,7 +223,7 @@
                     toScene = to,
                     currentScene = flowOf(to),
                     progress = MutableStateFlow(progress),
-                    isInitiatedByUserInput = true,
+                    isInitiatedByUserInput = isInitiatedByUserInput,
                     isUserInputOngoing = isUserInputOngoing,
                 )
             )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 1f2ecb7..3e9ae4d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -37,7 +37,7 @@
 class FakeUserRepository @Inject constructor() : UserRepository {
     companion object {
         // User id to represent a non system (human) user id. We presume this is the main user.
-        const val MAIN_USER_ID = 10
+        private const val MAIN_USER_ID = 10
 
         private const val DEFAULT_SELECTED_USER = 0
         private val DEFAULT_SELECTED_USER_INFO =
@@ -84,10 +84,6 @@
 
     override var isRefreshUsersPaused: Boolean = false
 
-    override suspend fun getMainUserId(): Int? {
-        return MAIN_USER_ID
-    }
-
     var refreshUsersCallCount: Int = 0
         private set
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt
index 9dddfcd..89672f1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt
@@ -17,8 +17,6 @@
 package com.android.systemui.user.domain.interactor
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.user.data.repository.userRepository
 
-val Kosmos.selectedUserInteractor by
-    Kosmos.Fixture { SelectedUserInteractor(applicationCoroutineScope, userRepository) }
+val Kosmos.selectedUserInteractor by Kosmos.Fixture { SelectedUserInteractor(userRepository) }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index e06f400..bc608c5 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -16,6 +16,38 @@
     visibility: ["//visibility:public"],
 }
 
+filegroup {
+    name: "ravenwood-services-policies",
+    srcs: [
+        "texts/ravenwood-services-policies.txt",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+filegroup {
+    name: "ravenwood-framework-policies",
+    srcs: [
+        "texts/ravenwood-framework-policies.txt",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+filegroup {
+    name: "ravenwood-standard-options",
+    srcs: [
+        "texts/ravenwood-standard-options.txt",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+filegroup {
+    name: "ravenwood-annotation-allowed-classes",
+    srcs: [
+        "texts/ravenwood-annotation-allowed-classes.txt",
+    ],
+    visibility: ["//visibility:public"],
+}
+
 java_library {
     name: "ravenwood-annotations-lib",
     srcs: [":ravenwood-annotations"],
diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh
index beacde2..cf58bd2 100755
--- a/ravenwood/scripts/ravenwood-stats-collector.sh
+++ b/ravenwood/scripts/ravenwood-stats-collector.sh
@@ -24,6 +24,8 @@
 # Where the input files are.
 path=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/ravenwood-stats-checker/x86_64/
 
+timestamp="$(date --iso-8601=seconds)"
+
 m() {
     ${ANDROID_BUILD_TOP}/build/soong/soong_ui.bash --make-mode "$@"
 }
@@ -39,15 +41,15 @@
     local jar=$1
     local file=$2
 
-    # Use sed to remove the header + prepend the jar filename.
-    sed -e '1d' -e "s/^/$jar,/" $file
+    # Remove the header row, and prepend the columns.
+    sed -e '1d' -e "s/^/$jar,$timestamp,/" $file
 }
 
 collect_stats() {
     local out="$1"
     {
         # Copy the header, with the first column appended.
-        echo -n "Jar,"
+        echo -n "Jar,Generated Date,"
         head -n 1 hoststubgen_framework-minus-apex_stats.csv
 
         dump "framework-minus-apex" hoststubgen_framework-minus-apex_stats.csv
@@ -61,7 +63,7 @@
     local out="$1"
     {
         # Copy the header, with the first column appended.
-        echo -n "Jar,"
+        echo -n "Jar,Generated Date,"
         head -n 1 hoststubgen_framework-minus-apex_apis.csv
 
         dump "framework-minus-apex"  hoststubgen_framework-minus-apex_apis.csv
diff --git a/ravenwood/texts/framework-minus-apex-ravenwood-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt
similarity index 100%
rename from ravenwood/texts/framework-minus-apex-ravenwood-policies.txt
rename to ravenwood/texts/ravenwood-framework-policies.txt
diff --git a/ravenwood/texts/services.core-ravenwood-policies.txt b/ravenwood/texts/ravenwood-services-policies.txt
similarity index 100%
rename from ravenwood/texts/services.core-ravenwood-policies.txt
rename to ravenwood/texts/ravenwood-services-policies.txt
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 5af9424..3cea014 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -4552,10 +4552,9 @@
             pw.println("           1: crop_windows");
             pw.println("           2: resizeable");
             pw.println("           3: resizeable_and_pipable");
-            pw.println("       resize <TASK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
-            pw.println("           Makes sure <TASK_ID> is in a stack with the specified bounds.");
-            pw.println("           Forces the task to be resizeable and creates a stack if no existing stack");
-            pw.println("           has the specified bounds.");
+            pw.println("       resize <TASK_ID> <LEFT> <TOP> <RIGHT> <BOTTOM>");
+            pw.println("           The task is resized only if it is in multi-window windowing");
+            pw.println("           mode or freeform windowing mode.");
             pw.println("  update-appinfo <USER_ID> <PACKAGE_NAME> [<PACKAGE_NAME>...]");
             pw.println("      Update the ApplicationInfo objects of the listed packages for <USER_ID>");
             pw.println("      without restarting any processes.");
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index a46975fb..11ef577 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -285,7 +285,8 @@
             List<BrightnessStateModifier> modifiers = new ArrayList<>();
             modifiers.add(new DisplayDimModifier(context));
             modifiers.add(new BrightnessLowPowerModeModifier());
-            if (flags.isEvenDimmerEnabled() && displayDeviceConfig != null) {
+            if (flags.isEvenDimmerEnabled() && displayDeviceConfig != null
+                    && displayDeviceConfig.isEvenDimmerAvailable()) {
                 modifiers.add(new BrightnessLowLuxModifier(handler, listener, context,
                         displayDeviceConfig));
             }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
index a3dfe22..7ba4a4d 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
@@ -87,9 +87,7 @@
                 mContentResolver, Settings.Secure.EVEN_DIMMER_MIN_NITS,
                 /* def= */ MIN_NITS_DEFAULT, userId);
 
-        boolean isActive = Settings.Secure.getFloatForUser(mContentResolver,
-                Settings.Secure.EVEN_DIMMER_ACTIVATED,
-                /* def= */ 0, userId) == 1.0f && mAutoBrightnessEnabled;
+        boolean isActive = isSettingEnabled() && mAutoBrightnessEnabled;
 
         float luxBasedNitsLowerBound = mDisplayDeviceConfig.getMinNitsFromLux(mAmbientLux);
 
@@ -202,6 +200,17 @@
         pw.println("  mMinNitsAllowed=" + mMinNitsAllowed);
     }
 
+    /**
+     * Defaults to true, on devices where setting is unset.
+     *
+     * @return if setting indicates feature is enabled
+     */
+    private boolean isSettingEnabled() {
+        return Settings.Secure.getFloatForUser(mContentResolver,
+                Settings.Secure.EVEN_DIMMER_ACTIVATED,
+                /* def= */ 1.0f, UserHandle.USER_CURRENT) == 1.0f;
+    }
+
     private float getBrightnessFromNits(float nits) {
         return mDisplayDeviceConfig.getBrightnessFromBacklight(
                 mDisplayDeviceConfig.getBacklightFromNits(nits));
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 61514ab..d2d0279 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -304,6 +304,10 @@
     // Make sure HdmiCecConfig is instantiated and the XMLs are read.
     private HdmiCecConfig mHdmiCecConfig;
 
+    // Timeout value for start ARC action after an established eARC connection was terminated,
+    // e.g. because eARC was disabled in Settings.
+    private static final int EARC_TRIGGER_START_ARC_ACTION_DELAY = 500;
+
     /**
      * Interface to report send result.
      */
@@ -5041,7 +5045,12 @@
             // AudioService here that the eARC connection is terminated.
             HdmiLogger.debug("eARC state change [new: HDMI_EARC_STATUS_ARC_PENDING(2)]");
             notifyEarcStatusToAudioService(false, new ArrayList<>());
-            startArcAction(true, null);
+            mHandler.postDelayed( new Runnable() {
+                @Override
+                public void run() {
+                    startArcAction(true, null);
+                }
+            }, EARC_TRIGGER_START_ARC_ACTION_DELAY);
             getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
                     oldEarcStatus, status, HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED);
         } else {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 14d04119..1b9d6c5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -307,14 +307,17 @@
     @MultiUserUnawareField
     private final InputMethodMenuController mMenuController;
     @MultiUserUnawareField
-    @NonNull private final AutofillSuggestionsController mAutofillController;
+    @NonNull
+    private final AutofillSuggestionsController mAutofillController;
 
     @GuardedBy("ImfLock.class")
     @MultiUserUnawareField
-    @NonNull private final ImeVisibilityStateComputer mVisibilityStateComputer;
+    @NonNull
+    private final ImeVisibilityStateComputer mVisibilityStateComputer;
 
     @GuardedBy("ImfLock.class")
-    @NonNull private final DefaultImeVisibilityApplier mVisibilityApplier;
+    @NonNull
+    private final DefaultImeVisibilityApplier mVisibilityApplier;
 
     /**
      * Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}.
@@ -363,7 +366,8 @@
     @MultiUserUnawareField
     private int mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
 
-    @Nullable private StatusBarManagerInternal mStatusBarManagerInternal;
+    @Nullable
+    private StatusBarManagerInternal mStatusBarManagerInternal;
     private boolean mShowOngoingImeSwitcherForPhones;
     @GuardedBy("ImfLock.class")
     @MultiUserUnawareField
@@ -538,7 +542,8 @@
      * The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
      */
     @MultiUserUnawareField
-    @Nullable IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
+    @Nullable
+    IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
 
     /**
      * The {@link EditorInfo} last provided by the current client.
@@ -804,7 +809,8 @@
             mRegistered = true;
         }
 
-        @Override public void onChange(boolean selfChange, Uri uri) {
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
             final Uri showImeUri = Settings.Secure.getUriFor(
                     Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
             final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
@@ -897,10 +903,10 @@
             }
             for (int userId : mUserManagerInternal.getUserIds()) {
                 final InputMethodSettings settings = queryInputMethodServicesInternal(
-                                mContext,
-                                userId,
-                                AdditionalSubtypeMapRepository.get(userId),
-                                DirectBootAwareness.AUTO);
+                        mContext,
+                        userId,
+                        AdditionalSubtypeMapRepository.get(userId),
+                        DirectBootAwareness.AUTO);
                 InputMethodSettingsRepository.put(userId, settings);
             }
             postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
@@ -1403,7 +1409,7 @@
             mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
             @SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
                     bindingControllerFactory = userId -> new InputMethodBindingController(userId,
-                            InputMethodManagerService.this);
+                    InputMethodManagerService.this);
             mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal,
                     bindingControllerForTesting != null ? bindingControllerForTesting
                             : bindingControllerFactory);
@@ -2416,7 +2422,8 @@
 
     @FunctionalInterface
     interface ImeDisplayValidator {
-        @DisplayImePolicy int getDisplayImePolicy(int displayId);
+        @DisplayImePolicy
+        int getDisplayImePolicy(int displayId);
     }
 
     /**
@@ -2711,7 +2718,7 @@
                             : null;
                     if (mStatusBarManagerInternal != null) {
                         mStatusBarManagerInternal.setIcon(mSlotIme, packageName, iconId, 0,
-                                contentDescription  != null
+                                contentDescription != null
                                         ? contentDescription.toString() : null);
                         mStatusBarManagerInternal.setIconVisibility(mSlotIme, true);
                     }
@@ -4705,7 +4712,7 @@
         SparseArray<IAccessibilityInputMethodSession> disabledSessions = new SparseArray<>();
         for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) {
             if (!accessibilitySessions.contains(mEnabledAccessibilitySessions.keyAt(i))) {
-                AccessibilitySessionState sessionState  = mEnabledAccessibilitySessions.valueAt(i);
+                AccessibilitySessionState sessionState = mEnabledAccessibilitySessions.valueAt(i);
                 if (sessionState != null) {
                     disabledSessions.append(mEnabledAccessibilitySessions.keyAt(i),
                             sessionState.mSession);
@@ -5440,7 +5447,7 @@
         }
         if (!settings.getMethodMap().containsKey(imeId)
                 || !settings.getEnabledInputMethodList().contains(
-                        settings.getMethodMap().get(imeId))) {
+                settings.getMethodMap().get(imeId))) {
             return false; // IME is not found or not enabled.
         }
         settings.putSelectedInputMethod(imeId);
@@ -6583,6 +6590,7 @@
         private final InputMethodManagerService mImms;
         @NonNull
         private final IBinder mToken;
+
         InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms,
                 @NonNull IBinder token) {
             mImms = imms;
@@ -6611,8 +6619,7 @@
         @Override
         public void createInputContentUriToken(Uri contentUri, String packageName,
                 AndroidFuture future /* T=IBinder */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<IBinder> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<IBinder> typedFuture = future;
             try {
                 typedFuture.complete(mImms.createInputContentUriToken(
                         mToken, contentUri, packageName).asBinder());
@@ -6630,8 +6637,7 @@
         @BinderThread
         @Override
         public void setInputMethod(String id, AndroidFuture future /* T=Void */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<Void> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
                 mImms.setInputMethod(mToken, id);
                 typedFuture.complete(null);
@@ -6644,8 +6650,7 @@
         @Override
         public void setInputMethodAndSubtype(String id, InputMethodSubtype subtype,
                 AndroidFuture future /* T=Void */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<Void> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
                 mImms.setInputMethodAndSubtype(mToken, id, subtype);
                 typedFuture.complete(null);
@@ -6659,8 +6664,7 @@
         public void hideMySoftInput(@NonNull ImeTracker.Token statsToken,
                 @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason,
                 AndroidFuture future /* T=Void */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<Void> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
                 mImms.hideMySoftInput(mToken, statsToken, flags, reason);
                 typedFuture.complete(null);
@@ -6674,8 +6678,7 @@
         public void showMySoftInput(@NonNull ImeTracker.Token statsToken,
                 @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason,
                 AndroidFuture future /* T=Void */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<Void> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
                 mImms.showMySoftInput(mToken, statsToken, flags, reason);
                 typedFuture.complete(null);
@@ -6693,8 +6696,7 @@
         @BinderThread
         @Override
         public void switchToPreviousInputMethod(AndroidFuture future /* T=Boolean */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<Boolean> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
             try {
                 typedFuture.complete(mImms.switchToPreviousInputMethod(mToken));
             } catch (Throwable e) {
@@ -6706,8 +6708,7 @@
         @Override
         public void switchToNextInputMethod(boolean onlyCurrentIme,
                 AndroidFuture future /* T=Boolean */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<Boolean> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
             try {
                 typedFuture.complete(mImms.switchToNextInputMethod(mToken, onlyCurrentIme));
             } catch (Throwable e) {
@@ -6718,8 +6719,7 @@
         @BinderThread
         @Override
         public void shouldOfferSwitchingToNextInputMethod(AndroidFuture future /* T=Boolean */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<Boolean> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
             try {
                 typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken));
             } catch (Throwable e) {
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 8a85328..71a7d0d 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -46,6 +46,7 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.LocalLog;
+import android.util.MutableBoolean;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.Xml;
@@ -104,6 +105,7 @@
     private final TelephonyManager mTelephonyManager;
     private final ArraySet<String> mBugreportAllowlistedPackages;
     private final BugreportFileManager mBugreportFileManager;
+    private static final FeatureFlags sFeatureFlags = new FeatureFlagsImpl();
 
 
     @GuardedBy("mLock")
@@ -429,9 +431,51 @@
         ensureUserCanTakeBugReport(bugreportMode);
 
         Slogf.i(TAG, "Starting bugreport for %s / %d", callingPackage, callingUid);
-        synchronized (mLock) {
-            startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd,
-                    bugreportMode, bugreportFlags, listener, isScreenshotRequested);
+        final MutableBoolean handoffLock = new MutableBoolean(false);
+        if (sFeatureFlags.asyncStartBugreport()) {
+            synchronized (handoffLock) {
+                new Thread(()-> {
+                    try {
+                        synchronized (mLock) {
+                            synchronized (handoffLock) {
+                                handoffLock.value = true;
+                                handoffLock.notifyAll();
+                            }
+                            startBugreportLocked(
+                                    callingUid,
+                                    callingPackage,
+                                    bugreportFd,
+                                    screenshotFd,
+                                    bugreportMode,
+                                    bugreportFlags,
+                                    listener,
+                                    isScreenshotRequested);
+                        }
+                    } catch (Exception e) {
+                        Slog.e(TAG, "Cannot start a new bugreport due to an unknown error", e);
+                        reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
+                    }
+                }, "BugreportManagerServiceThread").start();
+                try {
+                    while (!handoffLock.value) { // handle the rare case of a spurious wakeup
+                        handoffLock.wait(DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS);
+                    }
+                } catch (InterruptedException e) {
+                    Slog.e(TAG, "Unexpectedly interrupted waiting for startBugreportLocked", e);
+                }
+            }
+        } else {
+            synchronized (mLock) {
+                startBugreportLocked(
+                        callingUid,
+                        callingPackage,
+                        bugreportFd,
+                        screenshotFd,
+                        bugreportMode,
+                        bugreportFlags,
+                        listener,
+                        isScreenshotRequested);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/os/core_os_flags.aconfig b/services/core/java/com/android/server/os/core_os_flags.aconfig
index ae33df8..efdc9b8 100644
--- a/services/core/java/com/android/server/os/core_os_flags.aconfig
+++ b/services/core/java/com/android/server/os/core_os_flags.aconfig
@@ -7,3 +7,13 @@
     description: "Use proto tombstones as source of truth for adding to dropbox"
     bug: "323857385"
 }
+
+flag {
+    name: "async_start_bugreport"
+    namespace: "crumpet"
+    description: "Don't block callers on the start of dumpsys service"
+    bug: "180123623"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index 3862b79..5ac883c 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -688,6 +688,29 @@
         );
     }
 
+    /** Call intent should be handled by the main user. */
+    private static final DefaultCrossProfileIntentFilter CALL_PRIVATE_PROFILE =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    SKIP_CURRENT_PROFILE,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_CALL)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addDataScheme("tel")
+                    .addDataScheme("sip")
+                    .addDataScheme("voicemail")
+                    .build();
+
+    /** Pressing the call button should be handled by the main user. */
+    private static final DefaultCrossProfileIntentFilter CALL_BUTTON_PRIVATE_PROFILE =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    ONLY_IF_NO_MATCH_FOUND,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_CALL_BUTTON)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .build();
+
     /** Dial intent with mime type can be handled by either private profile or its parent user. */
     private static final DefaultCrossProfileIntentFilter DIAL_MIME_PRIVATE_PROFILE =
             new DefaultCrossProfileIntentFilter.Builder(
@@ -755,6 +778,10 @@
                 DIAL_MIME_PRIVATE_PROFILE,
                 DIAL_DATA_PRIVATE_PROFILE,
                 DIAL_RAW_PRIVATE_PROFILE,
+                CALL_PRIVATE_PROFILE,
+                CALL_BUTTON_PRIVATE_PROFILE,
+                EMERGENCY_CALL_DATA,
+                EMERGENCY_CALL_MIME,
                 SMS_MMS_PRIVATE_PROFILE
         );
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index fa54f6e..b369f03 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1667,15 +1667,6 @@
         if (appMetadataFile.exists()) {
             return true;
         }
-        if (isSystem) {
-            try {
-                makeDirRecursive(new File(appMetadataFilePath).getParentFile(), 0700);
-            } catch (Exception e) {
-                Slog.e(TAG, "Failed to create app metadata dir for package "
-                        + pkg.getPackageName() + ": " + e.getMessage());
-                return false;
-            }
-        }
         Map<String, Property> properties = pkg.getProperties();
         if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) {
             return false;
@@ -1684,6 +1675,15 @@
         if (!fileInAPkPathProperty.isString()) {
             return false;
         }
+        if (isSystem && !appMetadataFile.getParentFile().exists()) {
+            try {
+                makeDirRecursive(appMetadataFile.getParentFile(), 0700);
+            } catch (Exception e) {
+                Slog.e(TAG, "Failed to create app metadata dir for package "
+                        + pkg.getPackageName() + ": " + e.getMessage());
+                return false;
+            }
+        }
         String fileInApkPath = fileInAPkPathProperty.getString();
         List<AndroidPackageSplit> splits = pkg.getSplits();
         for (int i = 0; i < splits.size(); i++) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 82902d4..9edf3b1 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -172,7 +172,7 @@
     static final boolean DEBUG = false; // STOPSHIP if true
     static final boolean DEBUG_LOAD = false; // STOPSHIP if true
     static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
-    static final boolean DEBUG_REBOOT = false; // STOPSHIP if true
+    static final boolean DEBUG_REBOOT = true;
 
     @VisibleForTesting
     static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
@@ -3798,24 +3798,36 @@
                 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
                 final boolean archival = intent.getBooleanExtra(Intent.EXTRA_ARCHIVAL, false);
 
+                Slog.d(TAG, "received package broadcast intent: " + intent);
                 switch (action) {
                     case Intent.ACTION_PACKAGE_ADDED:
                         if (replacing) {
+                            Slog.d(TAG, "replacing package: " + packageName + " userId" + userId);
                             handlePackageUpdateFinished(packageName, userId);
                         } else {
+                            Slog.d(TAG, "adding package: " + packageName + " userId" + userId);
                             handlePackageAdded(packageName, userId);
                         }
                         break;
                     case Intent.ACTION_PACKAGE_REMOVED:
                         if (!replacing || (replacing && archival)) {
+                            if (!replacing) {
+                                Slog.d(TAG, "removing package: "
+                                        + packageName + " userId" + userId);
+                            } else if (archival) {
+                                Slog.d(TAG, "archiving package: "
+                                        + packageName + " userId" + userId);
+                            }
                             handlePackageRemoved(packageName, userId);
                         }
                         break;
                     case Intent.ACTION_PACKAGE_CHANGED:
+                        Slog.d(TAG, "changing package: " + packageName + " userId" + userId);
                         handlePackageChanged(packageName, userId);
-
                         break;
                     case Intent.ACTION_PACKAGE_DATA_CLEARED:
+                        Slog.d(TAG, "clearing data for package: "
+                                + packageName + " userId" + userId);
                         handlePackageDataCleared(packageName, userId);
                         break;
                 }
diff --git a/services/core/java/com/android/server/power/batterysaver/flags.aconfig b/services/core/java/com/android/server/power/batterysaver/flags.aconfig
index 059c4a4..1dea523 100644
--- a/services/core/java/com/android/server/power/batterysaver/flags.aconfig
+++ b/services/core/java/com/android/server/power/batterysaver/flags.aconfig
@@ -3,7 +3,7 @@
 
 flag {
   name: "update_auto_turn_on_notification_string_and_action"
-  namespace: "power_optimization"
+  namespace: "backstage_power"
   description: "Improve the string and hightligh settings item for battery saver auto-turn-on notification"
   bug: "336960905"
   metadata {
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index 3619253..47425322 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -115,6 +115,10 @@
     // validation failure.
     private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT = 12;
 
+    /** Carriers can disable the detector by setting the threshold to -1 */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR = -1;
+
     private static final int POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT = 20;
 
     // By default, there's no maximum limit enforced
@@ -271,7 +275,10 @@
         // When multiple parallel inbound transforms are created, NetworkMetricMonitor will be
         // enabled on the last one as a sample
         mInboundTransform = inboundTransform;
-        start();
+
+        if (!Flags.allowDisableIpsecLossDetector() || canStart()) {
+            start();
+        }
     }
 
     @Override
@@ -284,6 +291,14 @@
             mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
             mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
         }
+
+        if (Flags.allowDisableIpsecLossDetector() && canStart() != isStarted()) {
+            if (canStart()) {
+                start();
+            } else {
+                stop();
+            }
+        }
     }
 
     @Override
@@ -298,6 +313,12 @@
         mHandler.postDelayed(new PollIpSecStateRunnable(), mCancellationToken, 0L);
     }
 
+    private boolean canStart() {
+        return mInboundTransform != null
+                && mPacketLossRatePercentThreshold
+                        != IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR;
+    }
+
     @Override
     protected void start() {
         super.start();
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index 1c59977..6aa0039 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -80,8 +80,8 @@
             LaunchParamsController.LaunchParams outParams) {
 
         if (!canEnterDesktopMode(mContext)) {
-            appendLog("desktop mode is not enabled, continuing");
-            return RESULT_CONTINUE;
+            appendLog("desktop mode is not enabled, skipping");
+            return RESULT_SKIP;
         }
 
         if (task == null) {
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 8e78d25..6dec712 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -708,26 +708,6 @@
         }
     }
 
-    /**
-     * Removes the oldest recent task that is compatible with the given one. This is possible if
-     * the task windowing mode changed after being added to the Recents.
-     */
-    void removeCompatibleRecentTask(Task task) {
-        final int taskIndex = mTasks.indexOf(task);
-        if (taskIndex < 0) {
-            return;
-        }
-
-        final int candidateIndex = findRemoveIndexForTask(task, false /* includingSelf */);
-        if (candidateIndex == -1) {
-            // Nothing to trim
-            return;
-        }
-
-        final Task taskToRemove = taskIndex > candidateIndex ? task : mTasks.get(candidateIndex);
-        remove(taskToRemove);
-    }
-
     void removeTasksByPackageName(String packageName, int userId) {
         for (int i = mTasks.size() - 1; i >= 0; --i) {
             final Task task = mTasks.get(i);
@@ -1615,10 +1595,6 @@
      * list (if any).
      */
     private int findRemoveIndexForAddTask(Task task) {
-        return findRemoveIndexForTask(task, true /* includingSelf */);
-    }
-
-    private int findRemoveIndexForTask(Task task, boolean includingSelf) {
         final int recentsCount = mTasks.size();
         final Intent intent = task.intent;
         final boolean document = intent != null && intent.isDocument();
@@ -1674,8 +1650,6 @@
                     // existing task
                     continue;
                 }
-            } else if (!includingSelf) {
-                continue;
             }
             return i;
         }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5f13672..a9c47b8 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -6888,7 +6888,7 @@
             mIsBoosted = isBoosted;
             // The client transaction will be applied together with the next assignLayer.
             if (clientTransaction != null) {
-                mDecorSurfaceContainer.mPendingClientTransactions.add(clientTransaction);
+                mPendingClientTransactions.add(clientTransaction);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 1573d09..90e7bd7 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1916,7 +1916,6 @@
         final int count = tasksToReparent.size();
         for (int i = 0; i < count; ++i) {
             final Task task = tasksToReparent.get(i);
-            final int prevWindowingMode = task.getWindowingMode();
             if (syncId >= 0) {
                 addToSyncSet(syncId, task);
             }
@@ -1930,12 +1929,6 @@
                         hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
                         false /*moveParents*/, "processChildrenTaskReparentHierarchyOp");
             }
-            // Trim the compatible Recent task (if any) after the Task is reparented and now has
-            // a different windowing mode, in order to prevent redundant Recent tasks after
-            // reparenting.
-            if (prevWindowingMode != task.getWindowingMode()) {
-                mService.mTaskSupervisor.mRecentTasks.removeCompatibleRecentTask(task);
-            }
         }
 
         if (transition != null) transition.collect(newParent);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index dd173af..375fc5a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -349,8 +349,8 @@
 import android.app.admin.PreferentialNetworkServiceConfig;
 import android.app.admin.SecurityLog;
 import android.app.admin.SecurityLog.SecurityEvent;
+import android.app.admin.PackageSetPolicyValue;
 import android.app.admin.StartInstallingUpdateCallback;
-import android.app.admin.StringSetPolicyValue;
 import android.app.admin.SystemUpdateInfo;
 import android.app.admin.SystemUpdatePolicy;
 import android.app.admin.UnsafeStateException;
@@ -12073,7 +12073,7 @@
                 mDevicePolicyEngine.setLocalPolicy(
                         PolicyDefinition.PERMITTED_INPUT_METHODS,
                         admin,
-                        new StringSetPolicyValue(new HashSet<>(packageList)),
+                        new PackageSetPolicyValue(new HashSet<>(packageList)),
                         userId);
             }
         }
@@ -20358,12 +20358,12 @@
             mDevicePolicyEngine.setGlobalPolicy(
                     PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
                     enforcingAdmin,
-                    new StringSetPolicyValue(packages));
+                    new PackageSetPolicyValue(packages));
         } else {
             mDevicePolicyEngine.setLocalPolicy(
                     PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
                     enforcingAdmin,
-                    new StringSetPolicyValue(packages),
+                    new PackageSetPolicyValue(packages),
                     caller.getUserId());
         }
     }
@@ -24045,7 +24045,7 @@
                         mDevicePolicyEngine.setLocalPolicy(
                                 PolicyDefinition.PERMITTED_INPUT_METHODS,
                                 enforcingAdmin,
-                                new StringSetPolicyValue(
+                                new PackageSetPolicyValue(
                                         new HashSet<>(admin.permittedInputMethods)),
                                 admin.getUserHandle().getIdentifier());
                     }
@@ -24054,7 +24054,7 @@
                         mDevicePolicyEngine.setLocalPolicy(
                                 PolicyDefinition.PERMITTED_INPUT_METHODS,
                                 enforcingAdmin,
-                                new StringSetPolicyValue(
+                                new PackageSetPolicyValue(
                                         new HashSet<>(admin.getParentActiveAdmin()
                                                 .permittedInputMethods)),
                                 getProfileParentId(admin.getUserHandle().getIdentifier()));
@@ -24110,12 +24110,14 @@
                         mDevicePolicyEngine.setGlobalPolicy(
                                 PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
                                 enforcingAdmin,
-                                new StringSetPolicyValue(new HashSet<>(admin.protectedPackages)));
+                                new PackageSetPolicyValue(
+                                        new HashSet<>(admin.protectedPackages)));
                     } else {
                         mDevicePolicyEngine.setLocalPolicy(
                                 PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
                                 enforcingAdmin,
-                                new StringSetPolicyValue(new HashSet<>(admin.protectedPackages)),
+                                new PackageSetPolicyValue(
+                                        new HashSet<>(admin.protectedPackages)),
                                 admin.getUserHandle().getIdentifier());
                     }
                 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetPolicySerializer.java
similarity index 87%
rename from services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
rename to services/devicepolicy/java/com/android/server/devicepolicy/PackageSetPolicySerializer.java
index a9d65ac..c4da029 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetPolicySerializer.java
@@ -19,7 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.PolicyValue;
-import android.app.admin.StringSetPolicyValue;
+import android.app.admin.PackageSetPolicyValue;
 import android.util.Log;
 
 import com.android.modules.utils.TypedXmlPullParser;
@@ -30,7 +30,7 @@
 import java.util.Set;
 
 // TODO(scottjonathan): Replace with generic set implementation
-final class StringSetPolicySerializer extends PolicySerializer<Set<String>> {
+final class PackageSetPolicySerializer extends PolicySerializer<Set<String>> {
     private static final String ATTR_VALUES = "strings";
     private static final String ATTR_VALUES_SEPARATOR = ";";
     @Override
@@ -45,10 +45,10 @@
     PolicyValue<Set<String>> readFromXml(TypedXmlPullParser parser) {
         String valuesStr = parser.getAttributeValue(/* namespace= */ null, ATTR_VALUES);
         if (valuesStr == null) {
-            Log.e(DevicePolicyEngine.TAG, "Error parsing StringSet policy value.");
+            Log.e(DevicePolicyEngine.TAG, "Error parsing PackageSet policy value.");
             return null;
         }
         Set<String> values = Set.of(valuesStr.split(ATTR_VALUES_SEPARATOR));
-        return new StringSetPolicyValue(values);
+        return new PackageSetPolicyValue(values);
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetUnion.java
similarity index 79%
rename from services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java
rename to services/devicepolicy/java/com/android/server/devicepolicy/PackageSetUnion.java
index 5298960..d1e241b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetUnion.java
@@ -18,14 +18,15 @@
 
 import android.annotation.NonNull;
 import android.app.admin.PolicyValue;
-import android.app.admin.StringSetPolicyValue;
+import android.app.admin.PackageSetPolicyValue;
+import android.app.admin.StringSetUnion;
 
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Objects;
 import java.util.Set;
 
-final class StringSetUnion extends ResolutionMechanism<Set<String>> {
+final class PackageSetUnion extends ResolutionMechanism<Set<String>> {
 
     @Override
     PolicyValue<Set<String>> resolve(
@@ -38,17 +39,17 @@
         for (PolicyValue<Set<String>> policy : adminPolicies.values()) {
             unionOfPolicies.addAll(policy.getValue());
         }
-        return new StringSetPolicyValue(unionOfPolicies);
+        return new PackageSetPolicyValue(unionOfPolicies);
     }
 
     @Override
-    android.app.admin.StringSetUnion getParcelableResolutionMechanism() {
-        return new android.app.admin.StringSetUnion();
+    StringSetUnion getParcelableResolutionMechanism() {
+        return new StringSetUnion();
     }
 
 
     @Override
     public String toString() {
-        return "SetUnion {}";
+        return "PackageSetUnion {}";
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 9a73d5e..8d980b5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -162,9 +162,9 @@
             new PolicyDefinition<>(
                     new NoArgsPolicyKey(
                             DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY),
-                    new StringSetUnion(),
+                    new PackageSetUnion(),
                     PolicyEnforcerCallbacks::setUserControlDisabledPackages,
-                    new StringSetPolicySerializer());
+                    new PackageSetPolicySerializer());
 
     // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
     // actual policy with the correct arguments (i.e. packageName) when reading the policies from
@@ -328,7 +328,7 @@
             new MostRecent<>(),
             POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE,
             (Set<String> value, Context context, Integer userId, PolicyKey policyKey) -> true,
-            new StringSetPolicySerializer());
+            new PackageSetPolicySerializer());
 
 
     static PolicyDefinition<Boolean> SCREEN_CAPTURE_DISABLED = new PolicyDefinition<>(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 9a92c70..e1b66b5 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -106,6 +106,7 @@
     private HdmiPortInfo[] mHdmiPortInfo;
     private ArrayList<Integer> mLocalDeviceTypes = new ArrayList<>();
     private static final int PORT_ID_EARC_SUPPORTED = 3;
+    private static final int EARC_TRIGGER_START_ARC_ACTION_DELAY = 500;
 
     @Before
     public void setUp() throws Exception {
@@ -1374,6 +1375,11 @@
                 PORT_ID_EARC_SUPPORTED);
         verify(mHdmiControlServiceSpy, times(1))
                 .notifyEarcStatusToAudioService(eq(false), eq(new ArrayList<>()));
+        // ARC should be never initiated here. It should be started after 500 ms.
+        verify(mHdmiControlServiceSpy, times(0)).startArcAction(anyBoolean(), any());
+        // We move 500 ms forward because the action is only started 500 ms later.
+        mTestLooper.moveTimeForward(EARC_TRIGGER_START_ARC_ACTION_DELAY);
+        mTestLooper.dispatchAll();
         verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(true), any());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index f92387c..a268aa9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -79,19 +79,19 @@
 
     @Test
     @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-    public void testReturnsContinueIfDesktopWindowingIsDisabled() {
+    public void testReturnsSkipIfDesktopWindowingIsDisabled() {
         setupDesktopModeLaunchParamsModifier();
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(null).calculate());
+        assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(null).calculate());
     }
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-    public void testReturnsContinueIfDesktopWindowingIsEnabledOnUnsupportedDevice() {
+    public void testReturnsSkipIfDesktopWindowingIsEnabledOnUnsupportedDevice() {
         setupDesktopModeLaunchParamsModifier(/*isDesktopModeSupported=*/ false,
                 /*enforceDeviceRestrictions=*/ true);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(null).calculate());
+        assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(null).calculate());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 75b84d1..6ec1429 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1373,26 +1373,6 @@
         assertTrue(info.supportsMultiWindow);
     }
 
-    @Test
-    public void testRemoveCompatibleRecentTask() {
-        final Task task1 = createTaskBuilder(".Task").setWindowingMode(
-                WINDOWING_MODE_FULLSCREEN).build();
-        mRecentTasks.add(task1);
-        final Task task2 = createTaskBuilder(".Task").setWindowingMode(
-                WINDOWING_MODE_MULTI_WINDOW).build();
-        mRecentTasks.add(task2);
-        assertEquals(2, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
-                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
-
-        // Set windowing mode and ensure the same fullscreen task that created earlier is removed.
-        task2.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-        mRecentTasks.removeCompatibleRecentTask(task2);
-        assertEquals(1, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
-                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
-        assertEquals(task2.mTaskId, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
-                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().get(0).taskId);
-    }
-
     private TaskSnapshot createSnapshot(Point taskSize, Point bufferSize) {
         HardwareBuffer buffer = null;
         if (bufferSize != null) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4c719dd..bc8f65e 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3888,7 +3888,7 @@
 
     /**
      * Whether device resets all of NR timers when device is in a voice call and QOS is established.
-     * The default value is false;
+     * The default value is true;
      *
      * @see #KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING
      * @see #KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING
@@ -10909,7 +10909,7 @@
         sDefaults.putString(KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, "");
         sDefaults.putInt(KEY_NR_ADVANCED_BANDS_SECONDARY_TIMER_SECONDS_INT, 0);
         sDefaults.putBoolean(KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL, false);
-        sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL, false);
+        sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL, true);
         sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_PLMN_CHANGE_BOOL, false);
         /* Default value is 1 hour. */
         sDefaults.putLong(KEY_5G_WATCHDOG_TIME_MS_LONG, 3600000);
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index f161f31..0ecafc7 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -1283,6 +1283,8 @@
 
     private static final String JAPAN_ISO_COUNTRY_CODE = "JP";
 
+    private static final String SINGAPORE_ISO_COUNTRY_CODE = "SG";
+
     /**
      * Breaks the given number down and formats it according to the rules
      * for the country the number is from.
@@ -1669,6 +1671,17 @@
                  * dialing format.
                  */
                 result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL);
+            } else if (Flags.removeCountryCodeFromLocalSingaporeCalls() &&
+                    (SINGAPORE_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) &&
+                            pn.getCountryCode() ==
+                                    util.getCountryCodeForRegion(SINGAPORE_ISO_COUNTRY_CODE) &&
+                            (pn.getCountryCodeSource() ==
+                                    PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN))) {
+                /*
+                 * Need to reformat Singaporean phone numbers (when the user is in Singapore)
+                 * with the country code (+65) removed to comply with Singaporean regulations.
+                 */
+                result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL);
             } else {
                 result = util.formatInOriginalFormat(pn, defaultCountryIso);
             }
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
index c8b60e5..441a4ae 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -20,6 +20,7 @@
 import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY;
 import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY;
 
+import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR;
 import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.MIN_VALID_EXPECTED_RX_PACKET_NUM;
 import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.getMaxSeqNumIncreasePerSecond;
 import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
@@ -584,4 +585,56 @@
                 MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED,
                 getMaxSeqNumIncreasePerSecond(mCarrierConfig));
     }
+
+    private IpSecPacketLossDetector newDetectorAndSetTransform(int threshold) throws Exception {
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
+                        anyInt()))
+                .thenReturn(threshold);
+
+        final IpSecPacketLossDetector detector =
+                new IpSecPacketLossDetector(
+                        mVcnContext,
+                        mNetwork,
+                        mCarrierConfig,
+                        mMetricMonitorCallback,
+                        mDependencies);
+
+        detector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */);
+        detector.setInboundTransformInternal(mIpSecTransform);
+
+        return detector;
+    }
+
+    @Test
+    public void testDisableAndEnableDetectorWithCarrierConfig() throws Exception {
+        final IpSecPacketLossDetector detector =
+                newDetectorAndSetTransform(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR);
+
+        assertFalse(detector.isStarted());
+
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
+                        anyInt()))
+                .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD);
+        detector.setCarrierConfig(mCarrierConfig);
+
+        assertTrue(detector.isStarted());
+    }
+
+    @Test
+    public void testEnableAndDisableDetectorWithCarrierConfig() throws Exception {
+        final IpSecPacketLossDetector detector =
+                newDetectorAndSetTransform(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD);
+
+        assertTrue(detector.isStarted());
+
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
+                        anyInt()))
+                .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR);
+        detector.setCarrierConfig(mCarrierConfig);
+
+        assertFalse(detector.isStarted());
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index edad678..0439d5f5 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -123,6 +123,7 @@
         mSetFlagsRule.enableFlags(Flags.FLAG_VALIDATE_NETWORK_ON_IPSEC_LOSS);
         mSetFlagsRule.enableFlags(Flags.FLAG_EVALUATE_IPSEC_LOSS_ON_LP_NC_CHANGE);
         mSetFlagsRule.enableFlags(Flags.FLAG_HANDLE_SEQ_NUM_LEAP);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_DISABLE_IPSEC_LOSS_DETECTOR);
 
         when(mNetwork.getNetId()).thenReturn(-1);
 
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index f1e4ead..669cddb 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -443,7 +443,7 @@
   manifest_action.Action(AutoGenerateIsSplitRequired);
   manifest_action.Action(VerifyManifest);
   manifest_action.Action(FixCoreAppAttribute);
-  manifest_action.Action([&](xml::Element* el) -> bool {
+  manifest_action.Action([this, diag](xml::Element* el) -> bool {
     EnsureNamespaceIsDeclared("android", xml::kSchemaAndroid, &el->namespace_decls);
 
     if (options_.version_name_default) {
@@ -506,7 +506,7 @@
   manifest_action["eat-comment"];
 
   // Uses-sdk actions.
-  manifest_action["uses-sdk"].Action([&](xml::Element* el) -> bool {
+  manifest_action["uses-sdk"].Action([this](xml::Element* el) -> bool {
     if (options_.min_sdk_version_default &&
         el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion") == nullptr) {
       // There was no minSdkVersion defined and we have a default to assign.
@@ -528,7 +528,7 @@
 
   // Instrumentation actions.
   manifest_action["instrumentation"].Action(RequiredNameIsJavaClassName);
-  manifest_action["instrumentation"].Action([&](xml::Element* el) -> bool {
+  manifest_action["instrumentation"].Action([this](xml::Element* el) -> bool {
     if (!options_.rename_instrumentation_target_package) {
       return true;
     }
@@ -544,7 +544,7 @@
   manifest_action["attribution"];
   manifest_action["attribution"]["inherit-from"];
   manifest_action["original-package"];
-  manifest_action["overlay"].Action([&](xml::Element* el) -> bool {
+  manifest_action["overlay"].Action([this](xml::Element* el) -> bool {
     if (options_.rename_overlay_target_package) {
       if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "targetPackage")) {
         attr->value = options_.rename_overlay_target_package.value();
@@ -625,7 +625,7 @@
   uses_package_action["additional-certificate"];
 
   if (options_.debug_mode) {
-    application_action.Action([&](xml::Element* el) -> bool {
+    application_action.Action([](xml::Element* el) -> bool {
       xml::Attribute *attr = el->FindOrCreateAttribute(xml::kSchemaAndroid, "debuggable");
       attr->value = "true";
       return true;
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
index 5659a35..2e144f5 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
@@ -15,11 +15,11 @@
  */
 package com.android.hoststubgen.filters
 
-import com.android.hoststubgen.UnknownApiException
 import com.android.hoststubgen.addNonNullElement
 import com.android.hoststubgen.asm.ClassNodes
 import com.android.hoststubgen.asm.toHumanReadableClassName
 import com.android.hoststubgen.asm.toHumanReadableMethodName
+import com.android.hoststubgen.log
 
 // TODO: Validate all input names.
 
@@ -48,30 +48,30 @@
         return mPolicies[getClassKey(className)] ?: super.getPolicyForClass(className)
     }
 
-    private fun ensureClassExists(className: String) {
+    private fun checkClass(className: String) {
         if (classes.findClass(className) == null) {
-            throw UnknownApiException("Unknown class $className")
+            log.w("Unknown class $className")
         }
     }
 
-    private fun ensureFieldExists(className: String, fieldName: String) {
+    private fun checkField(className: String, fieldName: String) {
         if (classes.findField(className, fieldName) == null) {
-            throw UnknownApiException("Unknown field $className.$fieldName")
+            log.w("Unknown field $className.$fieldName")
         }
     }
 
-    private fun ensureMethodExists(
+    private fun checkMethod(
         className: String,
         methodName: String,
         descriptor: String
     ) {
         if (classes.findMethod(className, methodName, descriptor) == null) {
-            throw UnknownApiException("Unknown method $className.$methodName$descriptor")
+            log.w("Unknown method $className.$methodName$descriptor")
         }
     }
 
     fun setPolicyForClass(className: String, policy: FilterPolicyWithReason) {
-        ensureClassExists(className)
+        checkClass(className)
         mPolicies[getClassKey(className)] = policy
     }
 
@@ -81,7 +81,7 @@
     }
 
     fun setPolicyForField(className: String, fieldName: String, policy: FilterPolicyWithReason) {
-        ensureFieldExists(className, fieldName)
+        checkField(className, fieldName)
         mPolicies[getFieldKey(className, fieldName)] = policy
     }
 
@@ -100,7 +100,7 @@
             descriptor: String,
             policy: FilterPolicyWithReason,
             ) {
-        ensureMethodExists(className, methodName, descriptor)
+        checkMethod(className, methodName, descriptor)
         mPolicies[getMethodKey(className, methodName, descriptor)] = policy
     }
 
@@ -110,8 +110,8 @@
     }
 
     fun setRenameTo(className: String, methodName: String, descriptor: String, toName: String) {
-        ensureMethodExists(className, methodName, descriptor)
-        ensureMethodExists(className, toName, descriptor)
+        checkMethod(className, methodName, descriptor)
+        checkMethod(className, toName, descriptor)
         mRenames[getMethodKey(className, methodName, descriptor)] = toName
     }
 
@@ -121,7 +121,7 @@
     }
 
     fun setNativeSubstitutionClass(from: String, to: String) {
-        ensureClassExists(from)
+        checkClass(from)
 
         // Native substitute classes may be provided from other jars, so we can't do this check.
         // ensureClassExists(to)