Merge "Fix logging issue." into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 4f5e519..c92b4ff 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7701,7 +7701,7 @@
     method @Nullable public android.app.WallpaperColors getWallpaperColors(int);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.os.ParcelFileDescriptor getWallpaperFile(int);
     method public int getWallpaperId(int);
-    method public android.app.WallpaperInfo getWallpaperInfo();
+    method @RequiresPermission(value="QUERY_ALL_PACKAGES", conditional=true) public android.app.WallpaperInfo getWallpaperInfo();
     method @Nullable public android.app.WallpaperInfo getWallpaperInfo(int);
     method public boolean hasResourceWallpaper(@RawRes int);
     method public boolean isSetWallpaperAllowed();
@@ -22196,11 +22196,10 @@
 
   @FlaggedApi("android.media.audio.loudness_configurator_api") public class LoudnessCodecController implements java.lang.AutoCloseable {
     method @FlaggedApi("android.media.audio.loudness_configurator_api") public boolean addMediaCodec(@NonNull android.media.MediaCodec);
-    method public void close();
+    method @FlaggedApi("android.media.audio.loudness_configurator_api") public void close();
     method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int);
     method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int, @NonNull java.util.concurrent.Executor, @NonNull android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener);
     method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public android.os.Bundle getLoudnessCodecParams(@NonNull android.media.MediaCodec);
-    method @FlaggedApi("android.media.audio.loudness_configurator_api") public void release();
     method @FlaggedApi("android.media.audio.loudness_configurator_api") public void removeMediaCodec(@NonNull android.media.MediaCodec);
   }
 
@@ -41704,10 +41703,10 @@
     method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method @NonNull public android.os.ParcelUuid getCallId();
     method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+    method @FlaggedApi("com.android.server.telecom.flags.set_mute_state") public void requestMuteState(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method public void sendEvent(@NonNull String, @NonNull android.os.Bundle);
     method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
-    method @FlaggedApi("com.android.server.telecom.flags.set_mute_state") public void setMuteState(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method public void startCallStreaming(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0b17e03..ff2279c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -22,7 +22,7 @@
     field public static final String ACCESS_RCS_USER_CAPABILITY_EXCHANGE = "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE";
     field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES";
     field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
-    field public static final String ACCESS_SMARTSPACE = "android.permission.ACCESS_SMARTSPACE";
+    field @FlaggedApi("android.app.smartspace.flags.access_smartspace") public static final String ACCESS_SMARTSPACE = "android.permission.ACCESS_SMARTSPACE";
     field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
     field public static final String ACCESS_TUNED_INFO = "android.permission.ACCESS_TUNED_INFO";
     field public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER";
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e288b42..1bdbd4c5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6792,6 +6792,7 @@
                             }
                         }
                         if (killApp) {
+                            // Keep in sync with "perhaps it was removed" case below.
                             mPackages.remove(packages[i]);
                             mResourcePackages.remove(packages[i]);
                         }
@@ -6859,6 +6860,12 @@
                                 }
                             } catch (RemoteException e) {
                             }
+                        } else {
+                            // No package, perhaps it was removed?
+                            Slog.e(TAG, "Package [" + packages[i] + "] reported as REPLACED,"
+                                    + " but missing application info. Assuming REMOVED.");
+                            mPackages.remove(packages[i]);
+                            mResourcePackages.remove(packages[i]);
                         }
                     }
                 }
diff --git a/core/java/android/app/HomeVisibilityListener.java b/core/java/android/app/HomeVisibilityListener.java
index 1f5f2e4..5dd7ab0 100644
--- a/core/java/android/app/HomeVisibilityListener.java
+++ b/core/java/android/app/HomeVisibilityListener.java
@@ -69,6 +69,11 @@
     public HomeVisibilityListener() {
         mObserver = new android.app.IProcessObserver.Stub() {
             @Override
+            public void onProcessStarted(int pid, int processUid, int packageUid,
+                    String packageName, String processName) {
+            }
+
+            @Override
             public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
                 refreshHomeVisibility();
             }
diff --git a/core/java/android/app/IProcessObserver.aidl b/core/java/android/app/IProcessObserver.aidl
index 7be3620..5c5e72c 100644
--- a/core/java/android/app/IProcessObserver.aidl
+++ b/core/java/android/app/IProcessObserver.aidl
@@ -18,6 +18,17 @@
 
 /** {@hide} */
 oneway interface IProcessObserver {
+    /**
+     * Invoked when an app process starts up.
+     *
+     * @param pid The pid of the process.
+     * @param processUid The UID associated with the process.
+     * @param packageUid The UID associated with the package.
+     * @param packageName The name of the package.
+     * @param processName The name of the process.
+     */
+    void onProcessStarted(int pid, int processUid, int packageUid,
+                          @utf8InCpp String packageName, @utf8InCpp String processName);
     void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities);
     void onForegroundServicesChanged(int pid, int uid, int serviceTypes);
     void onProcessDied(int pid, int uid);
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 36b03c1..0116ca2 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1899,15 +1899,22 @@
 
     /**
      * Returns the information about the home screen wallpaper if its current wallpaper is a live
-     * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
+     * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
+     * caller doesn't have the appropriate permissions, this returns {@code null}.
      *
      * <p>
-     * In order to use this, apps should declare a {@code <queries>} tag with the action
-     * {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
+     * Before Android U, this method requires the
+     * {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission.
+     * </p>
+     *
+     * <p>
+     * Starting from Android U, in order to use this, apps should declare a {@code <queries>} tag
+     * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
      * this method will return {@code null} if the caller doesn't otherwise have
      * <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
      * </p>
      */
+    @RequiresPermission(value = "QUERY_ALL_PACKAGES", conditional = true)
     public WallpaperInfo getWallpaperInfo() {
         return getWallpaperInfoForUser(mContext.getUserId());
     }
@@ -1924,19 +1931,14 @@
     }
 
     /**
-     * Returns the information about the home screen wallpaper if its current wallpaper is a live
-     * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
+     * Returns the information about the designated wallpaper if its current wallpaper is a live
+     * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if
      * the caller doesn't have the appropriate permissions, this returns {@code null}.
      *
      * <p>
-     * Before Android U, this method requires the
-     * {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission.
-     * </p>
-     *
-     * <p>
-     * Starting from Android U, In order to use this, apps should declare a {@code <queries>} tag
-     * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
-     * this method will return {@code null} if the caller doesn't otherwise have
+     * In order to use this, apps should declare a {@code <queries>} tag with the action
+     * {@code "android.service.wallpaper.WallpaperService"}. Otherwise, this method will return
+     * {@code null} if the caller doesn't otherwise have
      * <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
      * </p>
      *
@@ -1952,7 +1954,7 @@
     /**
      * Returns the information about the designated wallpaper if its current wallpaper is a live
      * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
-     * the caller doesn't have the appropriate permissions, this returns {@code null}.
+     * caller doesn't have the appropriate permissions, this returns {@code null}.
      *
      * <p>
      * In order to use this, apps should declare a {@code <queries>} tag
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 325aa28f..83e18ec 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -133,4 +133,10 @@
      * device.
      */
     boolean isVirtualDeviceOwnedMirrorDisplay(int displayId);
+
+    /**
+     * Returns all current persistent device IDs, including the ones for which no virtual device
+     * exists, as long as one may have existed or can be created.
+     */
+    List<String> getAllPersistentDeviceIds();
 }
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index e4e9fba..903875b 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -153,3 +153,11 @@
     bug: "291135724"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "fix_system_apps_first_install_time"
+    namespace: "package_manager_service"
+    description: "Feature flag to fix the first-install timestamps for system apps."
+    bug: "321258605"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
index 73ac333..d51e62e 100644
--- a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
+++ b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
@@ -33,4 +33,20 @@
      * Defines behavior in response to authentication stopping
      */
     void onAuthenticationStopped();
+
+    /**
+     * Defines behavior in response to a successful authentication
+     * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+     *                      authentication
+     * @param userId The user Id for the requested authentication
+     */
+    void onAuthenticationSucceeded(int requestReason, int userId);
+
+    /**
+     * Defines behavior in response to a failed authentication
+     * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+     *                      authentication
+     * @param userId The user Id for the requested authentication
+     */
+    void onAuthenticationFailed(int requestReason, int userId);
 }
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index e267e6b..8e234fa 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -15,6 +15,7 @@
  */
 package android.hardware.face;
 
+import android.hardware.biometrics.AuthenticationStateListener;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IBiometricStateListener;
@@ -181,6 +182,14 @@
     // authenticators. The callback is automatically removed after it's invoked.
     void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
 
+    // Registers AuthenticationStateListener.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void registerAuthenticationStateListener(AuthenticationStateListener listener);
+
+    // Unregisters AuthenticationStateListener.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void unregisterAuthenticationStateListener(AuthenticationStateListener listener);
+
     // Registers BiometricStateListener.
     void registerBiometricStateListener(IBiometricStateListener listener);
 
diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java
index 8644d91..f65b713 100644
--- a/core/java/android/metrics/LogMaker.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -32,6 +32,7 @@
  * @hide
  */
 @SystemApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class LogMaker {
     private static final String TAG = "LogBuilder";
 
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 5871717..3977bdf 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -28,6 +28,7 @@
 import android.app.Application;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.sysprop.DeviceProperties;
 import android.sysprop.SocProperties;
 import android.sysprop.TelephonyProperties;
@@ -47,6 +48,7 @@
 /**
  * Information about the current build, extracted from system properties.
  */
+@RavenwoodKeepWholeClass
 public class Build {
     private static final String TAG = "Build";
 
@@ -307,7 +309,7 @@
          * compatibility.
          */
         final String[] abiList;
-        if (VMRuntime.getRuntime().is64Bit()) {
+        if (android.os.Process.is64Bit()) {
             abiList = SUPPORTED_64_BIT_ABIS;
         } else {
             abiList = SUPPORTED_32_BIT_ABIS;
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index aa283a2..a818919 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -20,6 +20,8 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass;
 import android.util.Log;
 import android.util.MutableInt;
 
@@ -36,6 +38,8 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
 
 /**
  * Gives access to the system properties store.  The system properties
@@ -51,6 +55,8 @@
  * {@hide}
  */
 @SystemApi
+@RavenwoodKeepWholeClass
+@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.SystemProperties_host")
 public class SystemProperties {
     private static final String TAG = "SystemProperties";
     private static final boolean TRACK_KEY_ACCESS = false;
@@ -94,6 +100,31 @@
         }
     }
 
+    /** @hide */
+    public static void init$ravenwood(Map<String, String> values,
+            Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate) {
+        native_init$ravenwood(values, keyReadablePredicate, keyWritablePredicate,
+                SystemProperties::callChangeCallbacks);
+        synchronized (sChangeCallbacks) {
+            sChangeCallbacks.clear();
+        }
+    }
+
+    /** @hide */
+    public static void reset$ravenwood() {
+        native_reset$ravenwood();
+        synchronized (sChangeCallbacks) {
+            sChangeCallbacks.clear();
+        }
+    }
+
+    // These native methods are currently only implemented by Ravenwood, as it's the only
+    // mechanism we have to jump to our RavenwoodNativeSubstitutionClass
+    private static native void native_init$ravenwood(Map<String, String> values,
+            Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate,
+            Runnable changeCallback);
+    private static native void native_reset$ravenwood();
+
     // The one-argument version of native_get used to be a regular native function. Nowadays,
     // we use the two-argument form of native_get all the time, but we can't just delete the
     // one-argument overload: apps use it via reflection, as the UnsupportedAppUsage annotation
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 5d7e04d..c0b4909 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -36,6 +36,7 @@
  * href="{@docRoot}tools/debugging/systrace.html">Analyzing Display and Performance
  * with Systrace</a>.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class Trace {
     /*
      * Writes trace events to the kernel trace buffer.  These trace events can be
@@ -123,10 +124,26 @@
 
     @UnsupportedAppUsage
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     private static native long nativeGetEnabledTags();
+    @android.ravenwood.annotation.RavenwoodReplace
     private static native void nativeSetAppTracingAllowed(boolean allowed);
+    @android.ravenwood.annotation.RavenwoodReplace
     private static native void nativeSetTracingEnabled(boolean allowed);
 
+    private static long nativeGetEnabledTags$ravenwood() {
+        // Tracing currently completely disabled under Ravenwood
+        return 0;
+    }
+
+    private static void nativeSetAppTracingAllowed$ravenwood(boolean allowed) {
+        // Tracing currently completely disabled under Ravenwood
+    }
+
+    private static void nativeSetTracingEnabled$ravenwood(boolean allowed) {
+        // Tracing currently completely disabled under Ravenwood
+    }
+
     @FastNative
     private static native void nativeTraceCounter(long tag, String name, long value);
     @FastNative
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 76e0c25..bbda068 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -271,7 +271,6 @@
         boolean mDrawingAllowed;
         boolean mOffsetsChanged;
         boolean mFixedSizeAllowed;
-        boolean mShouldDim;
         // Whether the wallpaper should be dimmed by default (when no additional dimming is applied)
         // based on its color hints
         boolean mShouldDimByDefault;
@@ -348,9 +347,11 @@
         private Display mDisplay;
         private Context mDisplayContext;
         private int mDisplayState;
-        private float mWallpaperDimAmount = 0.05f;
+
+        private float mCustomDimAmount = 0f;
+        private float mWallpaperDimAmount = 0f;
         private float mPreviousWallpaperDimAmount = mWallpaperDimAmount;
-        private float mDefaultDimAmount = mWallpaperDimAmount;
+        private float mDefaultDimAmount = 0.05f;
 
         SurfaceControl mSurfaceControl = new SurfaceControl();
         SurfaceControl mBbqSurfaceControl;
@@ -986,11 +987,8 @@
             mShouldDimByDefault = ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0
                     && (colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) == 0);
 
-            // If default dimming value changes and no additional dimming is applied
-            if (mShouldDimByDefault != mShouldDim && mWallpaperDimAmount == 0f) {
-                mShouldDim = mShouldDimByDefault;
-                updateSurfaceDimming();
-            }
+            // Recompute dim in case it changed compared to the previous WallpaperService
+            updateWallpaperDimming(mCustomDimAmount);
         }
 
         /**
@@ -999,28 +997,21 @@
          * @param dimAmount Float amount between [0.0, 1.0] to dim the wallpaper.
          */
         private void updateWallpaperDimming(float dimAmount) {
-            if (dimAmount == mWallpaperDimAmount) {
-                return;
-            }
+            mCustomDimAmount = Math.min(1f, dimAmount);
 
-            // Custom dim amount cannot be less than the default dim amount.
-            mWallpaperDimAmount = Math.max(mDefaultDimAmount, dimAmount);
-            // If dim amount is 0f (additional dimming is removed), then the wallpaper should dim
-            // based on its default wallpaper color hints.
-            mShouldDim = dimAmount != 0f || mShouldDimByDefault;
-            updateSurfaceDimming();
-        }
+            // If default dim is enabled, the actual dim amount is at least the default dim amount
+            mWallpaperDimAmount = (!mShouldDimByDefault) ? mCustomDimAmount
+                    : Math.max(mDefaultDimAmount, mCustomDimAmount);
 
-        private void updateSurfaceDimming() {
-            if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) {
+            if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null
+                    || mWallpaperDimAmount == mPreviousWallpaperDimAmount) {
                 return;
             }
 
             SurfaceControl.Transaction surfaceControlTransaction = new SurfaceControl.Transaction();
             // TODO: apply the dimming to preview as well once surface transparency works in
             // preview mode.
-            if ((!isPreview() && mShouldDim)
-                    || mPreviousWallpaperDimAmount != mWallpaperDimAmount) {
+            if (!isPreview()) {
                 Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount);
 
                 // Animate dimming to gradually change the wallpaper alpha from the previous
@@ -1545,8 +1536,6 @@
                     .createWindowContext(TYPE_WALLPAPER, null /* options */);
             mDefaultDimAmount = mDisplayContext.getResources().getFloat(
                     com.android.internal.R.dimen.config_wallpaperDimAmount);
-            mWallpaperDimAmount = mDefaultDimAmount;
-            mPreviousWallpaperDimAmount = mWallpaperDimAmount;
             mDisplayState = mDisplay.getCommittedState();
             mMergedConfiguration.setOverrideConfiguration(
                     mDisplayContext.getResources().getConfiguration());
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 17a3a12..7f1e037 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -34,11 +34,11 @@
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsController.LayoutInsetsDuringAnimation;
 import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ISIDE_BOTTOM;
-import static android.view.InsetsState.ISIDE_FLOATING;
-import static android.view.InsetsState.ISIDE_LEFT;
-import static android.view.InsetsState.ISIDE_RIGHT;
-import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsSource.SIDE_BOTTOM;
+import static android.view.InsetsSource.SIDE_NONE;
+import static android.view.InsetsSource.SIDE_LEFT;
+import static android.view.InsetsSource.SIDE_RIGHT;
+import static android.view.InsetsSource.SIDE_TOP;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -60,7 +60,7 @@
 import android.util.SparseIntArray;
 import android.util.SparseSetArray;
 import android.util.proto.ProtoOutputStream;
-import android.view.InsetsState.InternalInsetsSide;
+import android.view.InsetsSource.InternalInsetsSide;
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation.Bounds;
@@ -142,7 +142,7 @@
             if (mHasZeroInsetsIme) {
                 // IME has shownInsets of ZERO, and can't map to a side by default.
                 // Map zero insets IME to bottom, making it a special case of bottom insets.
-                idSideMap.put(ID_IME, ISIDE_BOTTOM);
+                idSideMap.put(ID_IME, SIDE_BOTTOM);
             }
             buildSideControlsMap(idSideMap, mSideControlsMap, controls);
         } else {
@@ -286,10 +286,10 @@
         }
         final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
         final ArrayList<SurfaceParams> params = new ArrayList<>();
-        updateLeashesForSide(ISIDE_LEFT, offset.left, params, outState, mPendingAlpha);
-        updateLeashesForSide(ISIDE_TOP, offset.top, params, outState, mPendingAlpha);
-        updateLeashesForSide(ISIDE_RIGHT, offset.right, params, outState, mPendingAlpha);
-        updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha);
+        updateLeashesForSide(SIDE_LEFT, offset.left, params, outState, mPendingAlpha);
+        updateLeashesForSide(SIDE_TOP, offset.top, params, outState, mPendingAlpha);
+        updateLeashesForSide(SIDE_RIGHT, offset.right, params, outState, mPendingAlpha);
+        updateLeashesForSide(SIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha);
 
         mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()]));
         mCurrentInsets = mPendingInsets;
@@ -499,19 +499,19 @@
         final float surfaceOffset = mTranslator != null
                 ? mTranslator.translateLengthInAppWindowToScreen(offset) : offset;
         switch (side) {
-            case ISIDE_LEFT:
+            case SIDE_LEFT:
                 m.postTranslate(-surfaceOffset, 0);
                 frame.offset(-offset, 0);
                 break;
-            case ISIDE_TOP:
+            case SIDE_TOP:
                 m.postTranslate(0, -surfaceOffset);
                 frame.offset(0, -offset);
                 break;
-            case ISIDE_RIGHT:
+            case SIDE_RIGHT:
                 m.postTranslate(surfaceOffset, 0);
                 frame.offset(offset, 0);
                 break;
-            case ISIDE_BOTTOM:
+            case SIDE_BOTTOM:
                 m.postTranslate(0, surfaceOffset);
                 frame.offset(0, offset);
                 break;
@@ -543,9 +543,10 @@
                 // control may be null if it got revoked.
                 continue;
             }
-            @InternalInsetsSide int side = InsetsState.getInsetSide(control.getInsetsHint());
-            if (side == ISIDE_FLOATING && control.getType() == WindowInsets.Type.ime()) {
-                side = ISIDE_BOTTOM;
+            @InternalInsetsSide int side = InsetsSource.getInsetSide(control.getInsetsHint());
+            if (side == SIDE_NONE && control.getType() == WindowInsets.Type.ime()) {
+                // IME might not provide insets when it is fullscreen or floating.
+                side = SIDE_BOTTOM;
             }
             sideControlsMap.add(side, control);
         }
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 0927d45..86ab213 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -46,6 +46,24 @@
  */
 public class InsetsSource implements Parcelable {
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "SIDE_", value = {
+            SIDE_NONE,
+            SIDE_LEFT,
+            SIDE_TOP,
+            SIDE_RIGHT,
+            SIDE_BOTTOM,
+            SIDE_UNKNOWN
+    })
+    public @interface InternalInsetsSide {}
+
+    static final int SIDE_NONE = 0;
+    static final int SIDE_LEFT = 1;
+    static final int SIDE_TOP = 2;
+    static final int SIDE_RIGHT = 3;
+    static final int SIDE_BOTTOM = 4;
+    static final int SIDE_UNKNOWN = 5;
+
     /** The insets source ID of IME */
     public static final int ID_IME = createId(null, 0, ime());
 
@@ -101,6 +119,12 @@
 
     private boolean mVisible;
 
+    /**
+     * Used to decide which side of the relative frame should receive insets when the frame fully
+     * covers the relative frame.
+     */
+    private @InternalInsetsSide int mSideHint = SIDE_NONE;
+
     private final Rect mTmpFrame = new Rect();
 
     public InsetsSource(int id, @InsetsType int type) {
@@ -119,6 +143,7 @@
                 ? new Rect(other.mVisibleFrame)
                 : null;
         mFlags = other.mFlags;
+        mSideHint = other.mSideHint;
     }
 
     public void set(InsetsSource other) {
@@ -128,6 +153,7 @@
                 ? new Rect(other.mVisibleFrame)
                 : null;
         mFlags = other.mFlags;
+        mSideHint = other.mSideHint;
     }
 
     public InsetsSource setFrame(int left, int top, int right, int bottom) {
@@ -160,6 +186,18 @@
         return this;
     }
 
+    /**
+     * Updates the side hint which is used to decide which side of the relative frame should receive
+     * insets when the frame fully covers the relative frame.
+     *
+     * @param bounds A rectangle which contains the frame. It will be used to calculate the hint.
+     */
+    public InsetsSource updateSideHint(Rect bounds) {
+        mSideHint = getInsetSide(
+                calculateInsets(bounds, mFrame, true /* ignoreVisibility */));
+        return this;
+    }
+
     public int getId() {
         return mId;
     }
@@ -236,8 +274,21 @@
             return Insets.of(0, 0, 0, mTmpFrame.height());
         }
 
-        // Intersecting at top/bottom
-        if (mTmpFrame.width() == relativeFrame.width()) {
+        if (mTmpFrame.equals(relativeFrame)) {
+            // Covering all sides
+            switch (mSideHint) {
+                default:
+                case SIDE_LEFT:
+                    return Insets.of(mTmpFrame.width(), 0, 0, 0);
+                case SIDE_TOP:
+                    return Insets.of(0, mTmpFrame.height(), 0, 0);
+                case SIDE_RIGHT:
+                    return Insets.of(0, 0, mTmpFrame.width(), 0);
+                case SIDE_BOTTOM:
+                    return Insets.of(0, 0, 0, mTmpFrame.height());
+            }
+        } else if (mTmpFrame.width() == relativeFrame.width()) {
+            // Intersecting at top/bottom
             if (mTmpFrame.top == relativeFrame.top) {
                 return Insets.of(0, mTmpFrame.height(), 0, 0);
             } else if (mTmpFrame.bottom == relativeFrame.bottom) {
@@ -249,9 +300,8 @@
             if (mTmpFrame.top == 0) {
                 return Insets.of(0, mTmpFrame.height(), 0, 0);
             }
-        }
-        // Intersecting at left/right
-        else if (mTmpFrame.height() == relativeFrame.height()) {
+        } else if (mTmpFrame.height() == relativeFrame.height()) {
+            // Intersecting at left/right
             if (mTmpFrame.left == relativeFrame.left) {
                 return Insets.of(mTmpFrame.width(), 0, 0, 0);
             } else if (mTmpFrame.right == relativeFrame.right) {
@@ -283,6 +333,46 @@
     }
 
     /**
+     * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
+     * is set in order that this method returns a meaningful result.
+     */
+    static @InternalInsetsSide int getInsetSide(Insets insets) {
+        if (Insets.NONE.equals(insets)) {
+            return SIDE_NONE;
+        }
+        if (insets.left != 0) {
+            return SIDE_LEFT;
+        }
+        if (insets.top != 0) {
+            return SIDE_TOP;
+        }
+        if (insets.right != 0) {
+            return SIDE_RIGHT;
+        }
+        if (insets.bottom != 0) {
+            return SIDE_BOTTOM;
+        }
+        return SIDE_UNKNOWN;
+    }
+
+    static String sideToString(@InternalInsetsSide int side) {
+        switch (side) {
+            case SIDE_NONE:
+                return "NONE";
+            case SIDE_LEFT:
+                return "LEFT";
+            case SIDE_TOP:
+                return "TOP";
+            case SIDE_RIGHT:
+                return "RIGHT";
+            case SIDE_BOTTOM:
+                return "BOTTOM";
+            default:
+                return "UNKNOWN:" + side;
+        }
+    }
+
+    /**
      * Creates an identifier of an {@link InsetsSource}.
      *
      * @param owner An object owned by the owner. Only the owner can modify its own sources.
@@ -331,7 +421,7 @@
     }
 
     public static String flagsToString(@Flags int flags) {
-        final StringJoiner joiner = new StringJoiner(" ");
+        final StringJoiner joiner = new StringJoiner("|");
         if ((flags & FLAG_SUPPRESS_SCRIM) != 0) {
             joiner.add("SUPPRESS_SCRIM");
         }
@@ -371,6 +461,7 @@
         }
         pw.print(" visible="); pw.print(mVisible);
         pw.print(" flags="); pw.print(flagsToString(mFlags));
+        pw.print(" sideHint="); pw.print(sideToString(mSideHint));
         pw.println();
     }
 
@@ -393,6 +484,7 @@
         if (mType != that.mType) return false;
         if (mVisible != that.mVisible) return false;
         if (mFlags != that.mFlags) return false;
+        if (mSideHint != that.mSideHint) return false;
         if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true;
         if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
         return mFrame.equals(that.mFrame);
@@ -400,7 +492,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags);
+        return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags, mSideHint);
     }
 
     public InsetsSource(Parcel in) {
@@ -414,6 +506,7 @@
         }
         mVisible = in.readBoolean();
         mFlags = in.readInt();
+        mSideHint = in.readInt();
     }
 
     @Override
@@ -434,6 +527,7 @@
         }
         dest.writeBoolean(mVisible);
         dest.writeInt(mFlags);
+        dest.writeInt(mSideHint);
     }
 
     @Override
@@ -442,7 +536,8 @@
                 + " mType=" + WindowInsets.Type.toString(mType)
                 + " mFrame=" + mFrame.toShortString()
                 + " mVisible=" + mVisible
-                + " mFlags=[" + flagsToString(mFlags) + "]"
+                + " mFlags=" + flagsToString(mFlags)
+                + " mSideHint=" + sideToString(mSideHint)
                 + "}";
     }
 
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 59e0932..c88da9e 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -37,7 +37,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.WindowConfiguration.ActivityType;
@@ -48,6 +47,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
+import android.view.InsetsSource.InternalInsetsSide;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
@@ -55,8 +55,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 import java.util.StringJoiner;
 
@@ -66,23 +64,6 @@
  */
 public class InsetsState implements Parcelable {
 
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "ISIDE", value = {
-            ISIDE_LEFT,
-            ISIDE_TOP,
-            ISIDE_RIGHT,
-            ISIDE_BOTTOM,
-            ISIDE_FLOATING,
-            ISIDE_UNKNOWN
-    })
-    public @interface InternalInsetsSide {}
-    static final int ISIDE_LEFT = 0;
-    static final int ISIDE_TOP = 1;
-    static final int ISIDE_RIGHT = 2;
-    static final int ISIDE_BOTTOM = 3;
-    static final int ISIDE_FLOATING = 4;
-    static final int ISIDE_UNKNOWN = 5;
-
     private final SparseArray<InsetsSource> mSources;
 
     /**
@@ -398,37 +379,14 @@
         }
 
         if (idSideMap != null) {
-            @InternalInsetsSide int insetSide = getInsetSide(insets);
-            if (insetSide != ISIDE_UNKNOWN) {
+            @InternalInsetsSide int insetSide = InsetsSource.getInsetSide(insets);
+            if (insetSide != InsetsSource.SIDE_UNKNOWN) {
                 idSideMap.put(source.getId(), insetSide);
             }
         }
     }
 
     /**
-     * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
-     * is set in order that this method returns a meaningful result.
-     */
-    static @InternalInsetsSide int getInsetSide(Insets insets) {
-        if (Insets.NONE.equals(insets)) {
-            return ISIDE_FLOATING;
-        }
-        if (insets.left != 0) {
-            return ISIDE_LEFT;
-        }
-        if (insets.top != 0) {
-            return ISIDE_TOP;
-        }
-        if (insets.right != 0) {
-            return ISIDE_RIGHT;
-        }
-        if (insets.bottom != 0) {
-            return ISIDE_BOTTOM;
-        }
-        return ISIDE_UNKNOWN;
-    }
-
-    /**
      * Gets the source mapped from the ID, or creates one if no such mapping has been made.
      */
     public InsetsSource getOrCreateSource(int id, int type) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3c36227..7bc832e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -11991,7 +11991,7 @@
                         Runnable timeoutRunnable = () -> Log.e(mTag,
                                 "Failed to submit the sync transaction after 4s. Likely to ANR "
                                         + "soon");
-                        mHandler.postDelayed(timeoutRunnable, 4L * Build.HW_TIMEOUT_MULTIPLIER);
+                        mHandler.postDelayed(timeoutRunnable, 4000L * Build.HW_TIMEOUT_MULTIPLIER);
                         transaction.addTransactionCommittedListener(mSimpleExecutor,
                                 () -> mHandler.removeCallbacks(timeoutRunnable));
                         surfaceSyncGroup.addTransaction(transaction);
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
index bf1d31c..fbb66d1 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
@@ -149,9 +149,12 @@
      *
      * Because it is not guaranteed that the events will be enqueued from a single thread, the
      * implementation must be thread-safe to prevent unexpected behaviour.
+     *
+     * @hide
      */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @NonNull
-    private final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue;
+    public final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue;
 
     /**
      * List of events held to be sent to the {@link ContentCaptureService} as a batch.
@@ -908,7 +911,7 @@
      * clear the buffer events then starting sending out current event.
      */
     private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) {
-        if (forceFlush) {
+        if (forceFlush || mEventProcessQueue.size() >= mManager.mOptions.maxBufferSize - 1) {
             // The buffer events are cleared in the same thread first to prevent new events
             // being added during the time of context switch. This would disrupt the sequence
             // of events.
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 9f9b7b4..1dd99ba 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -8,6 +8,15 @@
 }
 
 flag {
+    name: "enable_surface_native_alloc_registration_ro"
+    namespace: "toolkit"
+    description: "Feature flag for registering surfaces with the VM for faster"
+      " cleanup. Fixed readonly version."
+    bug: "306193257"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "enable_use_measure_cache_during_force_layout"
     namespace: "toolkit"
     description: "Enables using the measure cache during a view force layout from the second "
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index 5dbf328..93297e6 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -94,11 +94,18 @@
     @Nullable
     private final IBinder mPairedActivityToken;
 
+    /**
+     * If {@code true}, transitions are allowed even if the TaskFragment is empty. If
+     * {@code false}, transitions will wait until the TaskFragment becomes non-empty or other
+     * conditions are met. Default to {@code false}.
+     */
+    private final boolean mAllowTransitionWhenEmpty;
+
     private TaskFragmentCreationParams(
             @NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
             @NonNull IBinder ownerToken, @NonNull Rect initialRelativeBounds,
             @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
-            @Nullable IBinder pairedActivityToken) {
+            @Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty) {
         if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
             throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
                     + " pairedActivityToken should not be set at the same time.");
@@ -110,6 +117,7 @@
         mWindowingMode = windowingMode;
         mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
         mPairedActivityToken = pairedActivityToken;
+        mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
     }
 
     @NonNull
@@ -155,6 +163,11 @@
         return mPairedActivityToken;
     }
 
+    /** @hide */
+    public boolean getAllowTransitionWhenEmpty() {
+        return mAllowTransitionWhenEmpty;
+    }
+
     private TaskFragmentCreationParams(Parcel in) {
         mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
         mFragmentToken = in.readStrongBinder();
@@ -163,6 +176,7 @@
         mWindowingMode = in.readInt();
         mPairedPrimaryFragmentToken = in.readStrongBinder();
         mPairedActivityToken = in.readStrongBinder();
+        mAllowTransitionWhenEmpty = in.readBoolean();
     }
 
     /** @hide */
@@ -175,6 +189,7 @@
         dest.writeInt(mWindowingMode);
         dest.writeStrongBinder(mPairedPrimaryFragmentToken);
         dest.writeStrongBinder(mPairedActivityToken);
+        dest.writeBoolean(mAllowTransitionWhenEmpty);
     }
 
     @NonNull
@@ -201,6 +216,7 @@
                 + " windowingMode=" + mWindowingMode
                 + " pairedFragmentToken=" + mPairedPrimaryFragmentToken
                 + " pairedActivityToken=" + mPairedActivityToken
+                + " allowTransitionWhenEmpty=" + mAllowTransitionWhenEmpty
                 + "}";
     }
 
@@ -234,6 +250,8 @@
         @Nullable
         private IBinder mPairedActivityToken;
 
+        private boolean mAllowTransitionWhenEmpty;
+
         public Builder(@NonNull TaskFragmentOrganizerToken organizer,
                 @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
             mOrganizer = organizer;
@@ -298,12 +316,26 @@
             return this;
         }
 
+        /**
+         * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true},
+         * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions
+         * will wait until the TaskFragment becomes non-empty or other conditions are met. Default
+         * to {@code false}.
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setAllowTransitionWhenEmpty(boolean allowTransitionWhenEmpty) {
+            mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
+            return this;
+        }
+
         /** Constructs the options to create TaskFragment with. */
         @NonNull
         public TaskFragmentCreationParams build() {
             return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
                     mInitialRelativeBounds, mWindowingMode, mPairedPrimaryFragmentToken,
-                    mPairedActivityToken);
+                    mPairedActivityToken, mAllowTransitionWhenEmpty);
         }
     }
 }
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index cbf6367..edfbea4 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -50,3 +50,11 @@
   description: "Whether we should allow hiding the size compat restart button"
   bug: "318840081"
 }
+
+flag {
+  name: "configurable_font_scale_default"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether the font_scale is read from a device dependent configuration file"
+  bug: "319808237"
+  is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index eeea17b..90ca95a 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -71,7 +71,7 @@
                 "persist.debug.sysui.notification.notif_cooldown_t1", 60000);
         /** Value used by polite notif. feature */
         public static final Flag NOTIF_COOLDOWN_T2 = devFlag(
-                "persist.debug.sysui.notification.notif_cooldown_t2", 5000);
+                "persist.debug.sysui.notification.notif_cooldown_t2", 10000);
         /** Value used by polite notif. feature */
         public static final Flag NOTIF_VOLUME1 = devFlag(
                 "persist.debug.sysui.notification.notif_volume1", 30);
@@ -81,6 +81,10 @@
         public static final Flag NOTIF_COOLDOWN_COUNTER_RESET = devFlag(
                 "persist.debug.sysui.notification.notif_cooldown_counter_reset", 10);
 
+        /** Value used by polite notif. feature */
+        public static final Flag NOTIF_AVALANCHE_TIMEOUT = devFlag(
+                "persist.debug.sysui.notification.notif_avalanche_timeout", 120_000);
+
         /** b/303716154: For debugging only: use short bitmap duration. */
         public static final Flag DEBUG_SHORT_BITMAP_DURATION = devFlag(
                 "persist.sysui.notification.debug_short_bitmap_duration");
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index e58f4f0..88aa89a 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -34,6 +34,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class MetricsLogger {
     // define metric categories in frameworks/base/proto/src/metrics_constants.proto.
     // mirror changes in native version at system/core/libmetricslogger/metrics_logger.cpp
diff --git a/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java b/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
index 6786427..df8bf31 100644
--- a/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
+++ b/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
@@ -12,6 +12,7 @@
  *
  * @hide.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class FakeMetricsLogger extends MetricsLogger {
     private Queue<LogMaker> logs = new LinkedList<>();
 
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index e303890..6787ddc 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -27,6 +27,7 @@
  *
  * @hide.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class UiEventLoggerFake implements UiEventLogger {
     /**
      * Immutable data class used to record fake log events.
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 0704cb8..ae8f025 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -18,9 +18,11 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.os.Build;
 import android.os.Trace;
 import android.text.BoringLayout;
 import android.text.Layout;
+import android.text.PrecomputedText;
 import android.text.StaticLayout;
 import android.text.TextUtils;
 import android.text.method.TransformationMethod;
@@ -48,6 +50,10 @@
     private int mLayoutMaxLines = -1;
     private int mImageEndMargin;
 
+    private int mStaticLayoutCreationCountInOnMeasure = 0;
+
+    private static final boolean TRACE_ONMEASURE = Build.isDebuggable();
+
     public ImageFloatingTextView(Context context) {
         this(context, null);
     }
@@ -71,7 +77,10 @@
     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
             Layout.Alignment alignment, boolean shouldEllipsize,
             TextUtils.TruncateAt effectiveEllipsize, boolean useSaved) {
-        Trace.beginSection("ImageFloatingTextView#makeSingleLayout");
+        if (TRACE_ONMEASURE) {
+            Trace.beginSection("ImageFloatingTextView#makeSingleLayout");
+            mStaticLayoutCreationCountInOnMeasure++;
+        }
         TransformationMethod transformationMethod = getTransformationMethod();
         CharSequence text = getText();
         if (transformationMethod != null) {
@@ -79,7 +88,7 @@
         }
         text = text == null ? "" : text;
         StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(),
-                getPaint(), wantWidth)
+                        getPaint(), wantWidth)
                 .setAlignment(alignment)
                 .setTextDirection(getTextDirectionHeuristic())
                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
@@ -115,7 +124,10 @@
         }
 
         final StaticLayout result = builder.build();
-        Trace.endSection();
+        if (TRACE_ONMEASURE) {
+            trackMaxLines();
+            Trace.endSection();
+        }
         return result;
     }
 
@@ -141,7 +153,10 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        Trace.beginSection("ImageFloatingTextView#onMeasure");
+        if (TRACE_ONMEASURE) {
+            Trace.beginSection("ImageFloatingTextView#onMeasure");
+        }
+        mStaticLayoutCreationCountInOnMeasure = 0;
         int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mPaddingTop - mPaddingBottom;
         if (getLayout() != null && getLayout().getHeight() != availableHeight) {
             // We've been measured before and the new size is different than before, lets make sure
@@ -168,7 +183,12 @@
                 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
             }
         }
-        Trace.endSection();
+
+
+        if (TRACE_ONMEASURE) {
+            trackParameters();
+            Trace.endSection();
+        }
     }
 
     @Override
@@ -216,4 +236,37 @@
             requestLayout();
         }
     }
+
+    private void trackParameters() {
+        if (!TRACE_ONMEASURE) {
+            return;
+        }
+        Trace.setCounter("ImageFloatingView#staticLayoutCreationCount",
+                mStaticLayoutCreationCountInOnMeasure);
+        Trace.setCounter("ImageFloatingView#isPrecomputedText",
+                isTextAPrecomputedText());
+    }
+    /**
+     * @return 1 if {@link TextView#getText()} is PrecomputedText, else 0
+     */
+    private int isTextAPrecomputedText() {
+        final CharSequence text = getText();
+        if (text == null || text.isEmpty()) {
+            return 0;
+        }
+
+        if (text instanceof PrecomputedText) {
+            return 1;
+        }
+
+        return 0;
+    }
+
+    private void trackMaxLines() {
+        if (!TRACE_ONMEASURE) {
+            return;
+        }
+
+        Trace.setCounter("ImageFloatingView#layoutMaxLines", mLayoutMaxLines);
+    }
 }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 757978b..b5b3a48 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -333,11 +333,17 @@
 
     @UnsupportedAppUsage
     public LockPatternUtils(Context context) {
+        this(context, null);
+    }
+
+    @VisibleForTesting
+    public LockPatternUtils(Context context, ILockSettings lockSettings) {
         mContext = context;
         mContentResolver = context.getContentResolver();
 
         Looper looper = Looper.myLooper();
         mHandler = looper != null ? new Handler(looper) : null;
+        mLockSettingsService = lockSettings;
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index c06f5f7..e07acac 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.os.Build;
+import android.os.Trace;
 import android.util.AttributeSet;
 import android.view.RemotableViewMethod;
 import android.view.View;
@@ -45,6 +47,8 @@
 
     private int mMaxDisplayedLines = Integer.MAX_VALUE;
 
+    private static final boolean TRACE_ONMEASURE = Build.isDebuggable();
+
     public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
 
@@ -67,6 +71,10 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (TRACE_ONMEASURE) {
+            Trace.beginSection("MessagingLinearLayout#onMeasure");
+            trackMeasureSpecs(widthMeasureSpec, heightMeasureSpec);
+        }
         // This is essentially a bottom-up linear layout that only adds children that fit entirely
         // up to a maximum height.
         int targetHeight = MeasureSpec.getSize(heightMeasureSpec);
@@ -177,6 +185,9 @@
                 resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
                         widthMeasureSpec),
                 Math.max(getSuggestedMinimumHeight(), totalHeight));
+        if (TRACE_ONMEASURE) {
+            Trace.endSection();
+        }
     }
 
     @Override
@@ -240,6 +251,25 @@
         }
     }
 
+    private void trackMeasureSpecs(int widthMeasureSpec, int heightMeasureSpec) {
+        if (!TRACE_ONMEASURE) {
+            return;
+        }
+
+        final int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        Trace.setCounter("MessagingLinearLayout#onMeasure_widthMeasureSpecSize",
+                availableWidth);
+        Trace.setCounter("MessagingLinearLayout#onMeasure_widthMeasureSpecMode",
+                widthMode);
+        Trace.setCounter("MessagingLinearLayout#onMeasure_heightMeasureSpecSize",
+                availableHeight);
+        Trace.setCounter("MessagingLinearLayout#onMeasure_heightMeasureSpecMode",
+                heightMode);
+    }
+
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 7af69f2..6a640a5 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -28,6 +28,7 @@
 #include <meminfo/sysmeminfo.h>
 #include <processgroup/processgroup.h>
 #include <processgroup/sched_policy.h>
+#include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 
 #include <algorithm>
@@ -232,6 +233,31 @@
     }
 }
 
+// Look up the user ID of a process in /proc/${pid}/status. The Uid: line is present in
+// /proc/${pid}/status since at least kernel v2.5.
+static int uid_from_pid(int pid)
+{
+    int uid = -1;
+    std::array<char, 64> path;
+    int res = snprintf(path.data(), path.size(), "/proc/%d/status", pid);
+    if (res < 0 || res >= static_cast<int>(path.size())) {
+        DCHECK(false);
+        return uid;
+    }
+    FILE* f = fopen(path.data(), "r");
+    if (!f) {
+        return uid;
+    }
+    char line[256];
+    while (fgets(line, sizeof(line), f)) {
+        if (sscanf(line, "Uid: %d", &uid) == 1) {
+            break;
+        }
+    }
+    fclose(f);
+    return uid;
+}
+
 void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
 {
     ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp);
@@ -275,7 +301,12 @@
         }
     }
 
-    if (!SetProcessProfilesCached(0, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}))
+    const int uid = uid_from_pid(pid);
+    if (uid < 0) {
+        signalExceptionForGroupError(env, ESRCH, pid);
+        return;
+    }
+    if (!SetProcessProfilesCached(uid, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}))
         signalExceptionForGroupError(env, errno ? errno : EPERM, pid);
 }
 
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 25b2aaf..98f409a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -343,7 +343,9 @@
                   const std::vector<SurfaceControlStats>& /*stats*/) {
         JNIEnv* env = getenv();
         // Adding a strong reference for java SyncFence
-        presentFence->incStrong(0);
+        if (presentFence) {
+            presentFence->incStrong(0);
+        }
 
         jobject stats =
                 env->NewObject(gTransactionStatsClassInfo.clazz, gTransactionStatsClassInfo.ctor,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 100259e..070e2cb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7065,6 +7065,7 @@
         android:protectionLevel="signature" />
 
     <!-- @SystemApi Allows an application to access the smartspace service as a client.
+     @FlaggedApi(android.app.smartspace.flags.Flags.FLAG_ACCESS_SMARTSPACE)
      @hide  <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.ACCESS_SMARTSPACE"
         android:protectionLevel="signature|privileged|development" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index d1a90ae..4406302 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -212,8 +212,8 @@
         "src/android/database/CursorWindowTest.java",
         "src/android/os/**/*.java",
         "src/android/util/**/*.java",
+        "src/com/android/internal/logging/**/*.java",
         "src/com/android/internal/os/**/*.java",
-        "src/com/android/internal/os/LongArrayMultiStateCounterTest.java",
         "src/com/android/internal/util/**/*.java",
         "src/com/android/internal/power/EnergyConsumerStatsTest.java",
 
diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java
index 2d3e123..2a718ff 100644
--- a/core/tests/coretests/src/android/os/BuildTest.java
+++ b/core/tests/coretests/src/android/os/BuildTest.java
@@ -20,7 +20,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.platform.test.ravenwood.RavenwoodRule;
 
@@ -71,7 +70,6 @@
      */
     @Test
     @SmallTest
-    @IgnoreUnderRavenwood(blockedBy = Build.class)
     public void testBuildFields() throws Exception {
         assertNotEmpty("ID", Build.ID);
         assertNotEmpty("DISPLAY", Build.DISPLAY);
diff --git a/core/tests/coretests/src/android/os/TraceTest.java b/core/tests/coretests/src/android/os/TraceTest.java
index 593833ec..b2c005f 100644
--- a/core/tests/coretests/src/android/os/TraceTest.java
+++ b/core/tests/coretests/src/android/os/TraceTest.java
@@ -34,7 +34,6 @@
  * while tracing on the emulator and then run traceview to view the trace.
  */
 @RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = Trace.class)
 public class TraceTest {
     private static final String TAG = "TraceTest";
 
@@ -46,7 +45,51 @@
     private int gMethodCalls = 0;
 
     @Test
+    public void testEnableDisable() {
+        // Currently only verifying that we can invoke without crashing
+        Trace.setTracingEnabled(true, 0);
+        Trace.setTracingEnabled(false, 0);
+
+        Trace.setAppTracingAllowed(true);
+        Trace.setAppTracingAllowed(false);
+    }
+
+    @Test
+    public void testBeginEnd() {
+        // Currently only verifying that we can invoke without crashing
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG);
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+        Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, TAG, 42);
+        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+
+        Trace.beginSection(TAG);
+        Trace.endSection();
+
+        Trace.beginAsyncSection(TAG, 42);
+        Trace.endAsyncSection(TAG, 42);
+    }
+
+    @Test
+    public void testCounter() {
+        // Currently only verifying that we can invoke without crashing
+        Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+        Trace.setCounter(TAG, 42);
+    }
+
+    @Test
+    public void testInstant() {
+        // Currently only verifying that we can invoke without crashing
+        Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG);
+        Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, TAG);
+    }
+
+    @Test
     public void testNullStrings() {
+        // Currently only verifying that we can invoke without crashing
         Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, 42);
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, null);
 
@@ -62,6 +105,7 @@
 
     @Test
     @SmallTest
+    @IgnoreUnderRavenwood(blockedBy = Debug.class)
     public void testNativeTracingFromJava()
     {
         long start = System.currentTimeMillis();
@@ -82,6 +126,7 @@
     
     // This should not run in the automated suite.
     @Suppress
+    @IgnoreUnderRavenwood(blockedBy = Debug.class)
     public void disableTestNativeTracingFromC()
     {
         long start = System.currentTimeMillis();
@@ -97,6 +142,7 @@
     @Test
     @LargeTest
     @Suppress  // Failing.
+    @IgnoreUnderRavenwood(blockedBy = Debug.class)
     public void testMethodTracing()
     {
         long start = System.currentTimeMillis();
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 906d84e..672875a 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -20,8 +20,8 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ISIDE_BOTTOM;
-import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsSource.SIDE_BOTTOM;
+import static android.view.InsetsSource.SIDE_TOP;
 import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
 import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
 import static android.view.RoundedCorner.POSITION_TOP_LEFT;
@@ -106,8 +106,8 @@
                 typeSideMap);
         assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
         assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
-        assertEquals(ISIDE_TOP, typeSideMap.get(ID_STATUS_BAR));
-        assertEquals(ISIDE_BOTTOM, typeSideMap.get(ID_IME));
+        assertEquals(SIDE_TOP, typeSideMap.get(ID_STATUS_BAR));
+        assertEquals(SIDE_BOTTOM, typeSideMap.get(ID_IME));
         assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
         assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(ime()));
     }
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
index f0f3a96..0075128 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
@@ -433,6 +433,72 @@
         assertThat(session.mEvents).isEmpty();
     }
 
+    @Test
+    public void notifyViewAppearedBelowMaximumBufferSize() throws RemoteException {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ true);
+        MainContentCaptureSessionV2 session = createSession(options);
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.onSessionStarted(0x2, null);
+        for (int i = 0; i < BUFFER_SIZE - 1; i++) {
+            View view = prepareView(session);
+            session.notifyViewAppeared(session.newViewStructure(view));
+        }
+        mTestableLooper.processAllMessages();
+
+        verify(mMockContentCaptureDirectManager, times(0))
+                .sendEvents(any(), anyInt(), any());
+        assertThat(session.mEvents).isNull();
+        assertThat(session.mEventProcessQueue).hasSize(BUFFER_SIZE - 1);
+    }
+
+    @Test
+    public void notifyViewAppearedExactAsMaximumBufferSize() throws RemoteException {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ true);
+        MainContentCaptureSessionV2 session = createSession(options);
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.onSessionStarted(0x2, null);
+        for (int i = 0; i < BUFFER_SIZE; i++) {
+            View view = prepareView(session);
+            session.notifyViewAppeared(session.newViewStructure(view));
+        }
+        mTestableLooper.processAllMessages();
+
+        verify(mMockContentCaptureDirectManager, times(1))
+                .sendEvents(any(), anyInt(), any());
+        assertThat(session.mEvents).isEmpty();
+        assertThat(session.mEventProcessQueue).isEmpty();
+    }
+
+    @Test
+    public void notifyViewAppearedAboveMaximumBufferSize() throws RemoteException {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ true);
+        MainContentCaptureSessionV2 session = createSession(options);
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.onSessionStarted(0x2, null);
+        for (int i = 0; i < BUFFER_SIZE * 2 + 1; i++) {
+            View view = prepareView(session);
+            session.notifyViewAppeared(session.newViewStructure(view));
+        }
+        mTestableLooper.processAllMessages();
+
+        verify(mMockContentCaptureDirectManager, times(2))
+                .sendEvents(any(), anyInt(), any());
+        assertThat(session.mEvents).isEmpty();
+        assertThat(session.mEventProcessQueue).hasSize(1);
+    }
+
     /** Simulates the regular content capture events sequence. */
     private void notifyContentCaptureEvents(final MainContentCaptureSessionV2 session) {
         final ArrayList<Object> events = new ArrayList<>(
diff --git a/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java
new file mode 100644
index 0000000..7054cc0
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.metrics.LogMaker;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.testing.FakeMetricsLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MetricsLoggerTest {
+    private FakeMetricsLogger mLogger;
+
+    private static final int TEST_ACTION = 42;
+
+    @Before
+    public void setUp() throws Exception {
+        mLogger = new FakeMetricsLogger();
+    }
+
+    @Test
+    public void testEmpty() throws Exception {
+        assertThat(mLogger.getLogs().size()).isEqualTo(0);
+    }
+
+    @Test
+    public void testAction() throws Exception {
+        mLogger.action(TEST_ACTION);
+        assertThat(mLogger.getLogs().size()).isEqualTo(1);
+        final LogMaker event = mLogger.getLogs().peek();
+        assertThat(event.getType()).isEqualTo(MetricsProto.MetricsEvent.TYPE_ACTION);
+        assertThat(event.getCategory()).isEqualTo(TEST_ACTION);
+    }
+
+    @Test
+    public void testVisible() throws Exception {
+        // Limited testing to confirm we don't crash
+        mLogger.visible(TEST_ACTION);
+        mLogger.hidden(TEST_ACTION);
+        mLogger.visibility(TEST_ACTION, true);
+        mLogger.visibility(TEST_ACTION, false);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java
new file mode 100644
index 0000000..7840f71
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.testing.UiEventLoggerFake;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UiEventLoggerTest {
+    private UiEventLoggerFake mLogger;
+
+    private static final int TEST_EVENT_ID = 42;
+    private static final int TEST_INSTANCE_ID = 21;
+
+    private enum MyUiEventEnum implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Example event")
+        TEST_EVENT(TEST_EVENT_ID);
+
+        private final int mId;
+
+        MyUiEventEnum(int id) {
+            mId = id;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
+
+    private InstanceId TEST_INSTANCE = InstanceId.fakeInstanceId(TEST_INSTANCE_ID);
+
+    @Before
+    public void setUp() throws Exception {
+        mLogger = new UiEventLoggerFake();
+    }
+
+    @Test
+    public void testEmpty() throws Exception {
+        assertThat(mLogger.numLogs()).isEqualTo(0);
+    }
+
+    @Test
+    public void testSimple() throws Exception {
+        mLogger.log(MyUiEventEnum.TEST_EVENT);
+        assertThat(mLogger.numLogs()).isEqualTo(1);
+        assertThat(mLogger.eventId(0)).isEqualTo(TEST_EVENT_ID);
+    }
+
+    @Test
+    public void testWithInstance() throws Exception {
+        mLogger.log(MyUiEventEnum.TEST_EVENT, TEST_INSTANCE);
+        assertThat(mLogger.numLogs()).isEqualTo(1);
+        assertThat(mLogger.eventId(0)).isEqualTo(TEST_EVENT_ID);
+        assertThat(mLogger.get(0).instanceId.getId()).isEqualTo(TEST_INSTANCE_ID);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
index ec4c563..06d888b 100644
--- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -18,10 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -33,6 +37,9 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class MonotonicClockTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private final MockClock mClock = new MockClock();
     private File mFile;
 
@@ -70,6 +77,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(reason = "b/321832617")
     public void corruptedFile() throws IOException {
         // Create an invalid binary XML file to cause IOException: "Unexpected magic number"
         try (FileWriter w = new FileWriter(mFile)) {
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
index 1a668f7..b90480a 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -16,20 +16,279 @@
 
 package com.android.internal.widget;
 
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.app.trust.TrustManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.UserInfo;
+import android.os.Looper;
+import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@IgnoreUnderRavenwood(blockedBy = LockPatternUtils.class)
 public class LockPatternUtilsTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    private ILockSettings mLockSettings;
+    private static final int USER_ID = 1;
+    private static final int DEMO_USER_ID = 5;
+
+    private LockPatternUtils mLockPatternUtils;
+
+    private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode)
+            throws Exception {
+        mLockSettings = mock(ILockSettings.class);
+        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+        final MockContentResolver cr = new MockContentResolver(context);
+        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        when(context.getContentResolver()).thenReturn(cr);
+        Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
+
+        when(mLockSettings.getCredentialType(DEMO_USER_ID)).thenReturn(
+                isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+                         : LockPatternUtils.CREDENTIAL_TYPE_NONE);
+        when(mLockSettings.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED,
+                DEMO_USER_ID)).thenReturn((long) PASSWORD_QUALITY_MANAGED);
+        when(mLockSettings.hasSecureLockScreen()).thenReturn(true);
+        mLockPatternUtils = new LockPatternUtils(context, mLockSettings);
+
+        final UserInfo userInfo = mock(UserInfo.class);
+        when(userInfo.isDemo()).thenReturn(isDemoUser);
+        final UserManager um = mock(UserManager.class);
+        when(um.getUserInfo(DEMO_USER_ID)).thenReturn(userInfo);
+        when(context.getSystemService(Context.USER_SERVICE)).thenReturn(um);
+    }
+
+    @Test
+    public void isUserInLockDown() throws Exception {
+        configureTest(true, false, 2);
+
+        // GIVEN strong auth not required
+        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(STRONG_AUTH_NOT_REQUIRED);
+
+        // THEN user isn't in lockdown
+        assertFalse(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+        // GIVEN lockdown
+        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+
+        // THEN user is in lockdown
+        assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+        // GIVEN lockdown and lockout
+        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN | STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+        // THEN user is in lockdown
+        assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+    }
+
+    @Test
+    public void isLockScreenDisabled_isDemoUser_true() throws Exception {
+        configureTest(false, true, 2);
+        assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+    }
+
+    @Test
+    public void isLockScreenDisabled_isSecureAndDemoUser_false() throws Exception {
+        configureTest(true, true, 2);
+        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+    }
+
+    @Test
+    public void isLockScreenDisabled_isNotDemoUser_false() throws Exception {
+        configureTest(false, false, 2);
+        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+    }
+
+    @Test
+    public void isLockScreenDisabled_isNotInDemoMode_false() throws Exception {
+        configureTest(false, true, 0);
+        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+    }
+
+    @Test
+    public void testAddWeakEscrowToken() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+        int testUserId = 10;
+        IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
+        mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
+        verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
+    }
+
+    @Test
+    public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+        mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
+        verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
+    }
+
+    @Test
+    public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+        mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
+        verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
+    }
+
+    @Test
+    public void testRemoveAutoEscrowToken() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        int testUserId = 10;
+        long testHandle = 100L;
+        mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
+        verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
+    }
+
+    @Test
+    public void testIsAutoEscrowTokenActive() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        int testUserId = 10;
+        long testHandle = 100L;
+        mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
+        verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
+    }
+
+    @Test
+    public void testIsAutoEscrowTokenValid() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        int testUserId = 10;
+        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+        long testHandle = 100L;
+        mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
+        verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
+    }
+
+    @Test
+    public void testSetEnabledTrustAgents() throws RemoteException {
+        int testUserId = 10;
+        ILockSettings ils = createTestLockSettings();
+        ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+        doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+        List<ComponentName> enabledTrustAgents = Lists.newArrayList(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+        mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId);
+
+        assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+    }
+
+    @Test
+    public void testGetEnabledTrustAgents() throws RemoteException {
+        int testUserId = 10;
+        ILockSettings ils = createTestLockSettings();
+        when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+                "com.android/.TrustAgent,com.test/.TestAgent");
+
+        List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId);
+
+        assertThat(trustAgents).containsExactly(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
+    }
+
+    @Test
+    public void testSetKnownTrustAgents() throws RemoteException {
+        int testUserId = 10;
+        ILockSettings ils = createTestLockSettings();
+        ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+        doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+        List<ComponentName> knownTrustAgents = Lists.newArrayList(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+        mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId);
+
+        assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+    }
+
+    @Test
+    public void testGetKnownTrustAgents() throws RemoteException {
+        int testUserId = 10;
+        ILockSettings ils = createTestLockSettings();
+        when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+                "com.android/.TrustAgent,com.test/.TestAgent");
+
+        List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId);
+
+        assertThat(trustAgents).containsExactly(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
+    }
+
+    @Test
+    public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue()
+            throws RemoteException {
+        TestStrongAuthTracker tracker = createStrongAuthTracker();
+        tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED);
+
+        assertTrue(tracker.isBiometricAllowedForUser(
+                /* isStrongBiometric = */ true,
+                DEMO_USER_ID));
+    }
+
+    @Test
+    public void isBiometricAllowedForUser_afterLockout_returnsFalse()
+            throws RemoteException {
+        TestStrongAuthTracker tracker = createStrongAuthTracker();
+        tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+        assertFalse(tracker.isBiometricAllowedForUser(
+                /* isStrongBiometric = */ true,
+                DEMO_USER_ID));
+    }
 
     @Test
     public void testUserFrp_isNotRegularUser() throws Exception {
@@ -56,4 +315,49 @@
         assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_REPAIR_MODE);
         assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_REPAIR_MODE);
     }
+
+    private TestStrongAuthTracker createStrongAuthTracker() {
+        final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
+        return new TestStrongAuthTracker(context, Looper.getMainLooper());
+    }
+
+    private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+
+        TestStrongAuthTracker(Context context, Looper looper) {
+            super(context, looper);
+        }
+
+        public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) {
+            handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID);
+        }
+    }
+
+    private ILockSettings createTestLockSettings() {
+        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+        final TrustManager trustManager = mock(TrustManager.class);
+        when(context.getSystemService(Context.TRUST_SERVICE)).thenReturn(trustManager);
+
+        final ILockSettings ils = mock(ILockSettings.class);
+        mLockPatternUtils = new LockPatternUtils(context, ils);
+        return ils;
+    }
+
+    private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
+        return new IWeakEscrowTokenActivatedListener.Stub() {
+            @Override
+            public void onWeakEscrowTokenActivated(long handle, int userId) {
+                // Do nothing.
+            }
+        };
+    }
+
+    private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
+        return new IWeakEscrowTokenRemovedListener.Stub() {
+            @Override
+            public void onWeakEscrowTokenRemoved(long handle, int userId) {
+                // Do nothing.
+            }
+        };
+    }
 }
diff --git a/core/tests/systemproperties/Android.bp b/core/tests/systemproperties/Android.bp
index 765ca3e..21aa3c44 100644
--- a/core/tests/systemproperties/Android.bp
+++ b/core/tests/systemproperties/Android.bp
@@ -15,6 +15,9 @@
     static_libs: [
         "android-common",
         "frameworks-core-util-lib",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "ravenwood-junit",
     ],
     libs: [
         "android.test.runner",
@@ -23,3 +26,22 @@
     platform_apis: true,
     certificate: "platform",
 }
+
+android_ravenwood_test {
+    name: "FrameworksCoreSystemPropertiesTestsRavenwood",
+    static_libs: [
+        "android-common",
+        "frameworks-core-util-lib",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "ravenwood-junit",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    auto_gen_config: true,
+}
diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
index 67783bf..ea65de0 100644
--- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
+++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
@@ -16,19 +16,36 @@
 
 package android.os;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
 
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-public class SystemPropertiesTest extends TestCase {
+public class SystemPropertiesTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setSystemPropertyMutable(KEY, null)
+            .setSystemPropertyMutable(UNSET_KEY, null)
+            .setSystemPropertyMutable(PERSIST_KEY, null)
+            .build();
+
     private static final String KEY = "sys.testkey";
     private static final String UNSET_KEY = "Aiw7woh6ie4toh7W";
     private static final String PERSIST_KEY = "persist.sys.testkey";
 
+    @Test
     @SmallTest
     public void testStressPersistPropertyConsistency() throws Exception {
         for (int i = 0; i < 100; ++i) {
@@ -38,6 +55,7 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testStressMemoryPropertyConsistency() throws Exception {
         for (int i = 0; i < 100; ++i) {
@@ -47,6 +65,7 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testProperties() throws Exception {
         String value;
@@ -93,6 +112,7 @@
       assertEquals(expected, value);
     }
 
+    @Test
     @SmallTest
     public void testHandle() throws Exception {
         String value;
@@ -114,6 +134,7 @@
         assertEquals(12345, handle.getInt(12345));
     }
 
+    @Test
     @SmallTest
     public void testIntegralProperties() throws Exception {
         testInt("", 123, 123);
@@ -133,6 +154,7 @@
         testLong("-3147483647", 124, -3147483647L);
     }
 
+    @Test
     @SmallTest
     public void testUnset() throws Exception {
         assertEquals("abc", SystemProperties.get(UNSET_KEY, "abc"));
@@ -142,6 +164,7 @@
         assertEquals(-10, SystemProperties.getLong(UNSET_KEY, -10));
     }
 
+    @Test
     @SmallTest
     @SuppressWarnings("null")
     public void testNullKey() throws Exception {
@@ -176,6 +199,7 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testCallbacks() {
         // Latches are not really necessary, but are easy to use.
@@ -220,6 +244,7 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testDigestOf() {
         final String empty = SystemProperties.digestOf();
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
deleted file mode 100644
index dcaf676..0000000
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright (C) 2017 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.util;
-
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.UserInfo;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserManager;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
-import android.platform.test.ravenwood.RavenwoodRule;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.widget.ILockSettings;
-import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
-import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
-import com.android.internal.widget.LockPatternUtils;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@IgnoreUnderRavenwood(blockedBy = LockPatternUtils.class)
-public class LockPatternUtilsTest {
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
-    private ILockSettings mLockSettings;
-    private static final int USER_ID = 1;
-    private static final int DEMO_USER_ID = 5;
-
-    private LockPatternUtils mLockPatternUtils;
-
-    private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode)
-            throws Exception {
-        mLockSettings = Mockito.mock(ILockSettings.class);
-        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
-
-        final MockContentResolver cr = new MockContentResolver(context);
-        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
-        when(context.getContentResolver()).thenReturn(cr);
-        Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
-
-        when(mLockSettings.getCredentialType(DEMO_USER_ID)).thenReturn(
-                isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
-                         : LockPatternUtils.CREDENTIAL_TYPE_NONE);
-        when(mLockSettings.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED,
-                DEMO_USER_ID)).thenReturn((long) PASSWORD_QUALITY_MANAGED);
-        // TODO(b/63758238): stop spying the class under test
-        mLockPatternUtils = spy(new LockPatternUtils(context));
-        when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings);
-        doReturn(true).when(mLockPatternUtils).hasSecureLockScreen();
-
-        final UserInfo userInfo = Mockito.mock(UserInfo.class);
-        when(userInfo.isDemo()).thenReturn(isDemoUser);
-        final UserManager um = Mockito.mock(UserManager.class);
-        when(um.getUserInfo(DEMO_USER_ID)).thenReturn(userInfo);
-        when(context.getSystemService(Context.USER_SERVICE)).thenReturn(um);
-    }
-
-    @Test
-    public void isUserInLockDown() throws Exception {
-        configureTest(true, false, 2);
-
-        // GIVEN strong auth not required
-        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(STRONG_AUTH_NOT_REQUIRED);
-
-        // THEN user isn't in lockdown
-        assertFalse(mLockPatternUtils.isUserInLockdown(USER_ID));
-
-        // GIVEN lockdown
-        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
-                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
-
-        // THEN user is in lockdown
-        assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
-
-        // GIVEN lockdown and lockout
-        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
-                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN | STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
-
-        // THEN user is in lockdown
-        assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
-    }
-
-    @Test
-    public void isLockScreenDisabled_isDemoUser_true() throws Exception {
-        configureTest(false, true, 2);
-        assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
-    }
-
-    @Test
-    public void isLockScreenDisabled_isSecureAndDemoUser_false() throws Exception {
-        configureTest(true, true, 2);
-        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
-    }
-
-    @Test
-    public void isLockScreenDisabled_isNotDemoUser_false() throws Exception {
-        configureTest(false, false, 2);
-        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
-    }
-
-    @Test
-    public void isLockScreenDisabled_isNotInDemoMode_false() throws Exception {
-        configureTest(false, true, 0);
-        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
-    }
-
-    @Test
-    public void testAddWeakEscrowToken() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
-        int testUserId = 10;
-        IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
-        mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
-        verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
-    }
-
-    @Test
-    public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
-        mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
-        verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
-    }
-
-    @Test
-    public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
-        mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
-        verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
-    }
-
-    @Test
-    public void testRemoveAutoEscrowToken() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        int testUserId = 10;
-        long testHandle = 100L;
-        mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
-        verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
-    }
-
-    @Test
-    public void testIsAutoEscrowTokenActive() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        int testUserId = 10;
-        long testHandle = 100L;
-        mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
-        verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
-    }
-
-    @Test
-    public void testIsAutoEscrowTokenValid() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        int testUserId = 10;
-        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
-        long testHandle = 100L;
-        mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
-        verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
-    }
-
-    @Test
-    public void testSetEnabledTrustAgents() throws RemoteException {
-        int testUserId = 10;
-        ILockSettings ils = createTestLockSettings();
-        ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
-        doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
-        List<ComponentName> enabledTrustAgents = Lists.newArrayList(
-                ComponentName.unflattenFromString("com.android/.TrustAgent"),
-                ComponentName.unflattenFromString("com.test/.TestAgent"));
-
-        mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId);
-
-        assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
-    }
-
-    @Test
-    public void testGetEnabledTrustAgents() throws RemoteException {
-        int testUserId = 10;
-        ILockSettings ils = createTestLockSettings();
-        when(ils.getString(anyString(), any(), anyInt())).thenReturn(
-                "com.android/.TrustAgent,com.test/.TestAgent");
-
-        List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId);
-
-        assertThat(trustAgents).containsExactly(
-                ComponentName.unflattenFromString("com.android/.TrustAgent"),
-                ComponentName.unflattenFromString("com.test/.TestAgent"));
-    }
-
-    @Test
-    public void testSetKnownTrustAgents() throws RemoteException {
-        int testUserId = 10;
-        ILockSettings ils = createTestLockSettings();
-        ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
-        doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
-        List<ComponentName> knownTrustAgents = Lists.newArrayList(
-                ComponentName.unflattenFromString("com.android/.TrustAgent"),
-                ComponentName.unflattenFromString("com.test/.TestAgent"));
-
-        mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId);
-
-        assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
-    }
-
-    @Test
-    public void testGetKnownTrustAgents() throws RemoteException {
-        int testUserId = 10;
-        ILockSettings ils = createTestLockSettings();
-        when(ils.getString(anyString(), any(), anyInt())).thenReturn(
-                "com.android/.TrustAgent,com.test/.TestAgent");
-
-        List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId);
-
-        assertThat(trustAgents).containsExactly(
-                ComponentName.unflattenFromString("com.android/.TrustAgent"),
-                ComponentName.unflattenFromString("com.test/.TestAgent"));
-    }
-
-    @Test
-    public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue()
-            throws RemoteException {
-        TestStrongAuthTracker tracker = createStrongAuthTracker();
-        tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED);
-
-        assertTrue(tracker.isBiometricAllowedForUser(
-                /* isStrongBiometric = */ true,
-                DEMO_USER_ID));
-    }
-
-    @Test
-    public void isBiometricAllowedForUser_afterLockout_returnsFalse()
-            throws RemoteException {
-        TestStrongAuthTracker tracker = createStrongAuthTracker();
-        tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
-
-        assertFalse(tracker.isBiometricAllowedForUser(
-                /* isStrongBiometric = */ true,
-                DEMO_USER_ID));
-    }
-
-
-    private TestStrongAuthTracker createStrongAuthTracker() {
-        final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
-        return new TestStrongAuthTracker(context, Looper.getMainLooper());
-    }
-
-    private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
-
-        TestStrongAuthTracker(Context context, Looper looper) {
-            super(context, looper);
-        }
-
-        public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) {
-            handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID);
-        }
-    }
-
-    private ILockSettings createTestLockSettings() {
-        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
-        mLockPatternUtils = spy(new LockPatternUtils(context));
-        final ILockSettings ils = Mockito.mock(ILockSettings.class);
-        when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
-        return ils;
-    }
-
-    private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
-        return new IWeakEscrowTokenActivatedListener.Stub() {
-            @Override
-            public void onWeakEscrowTokenActivated(long handle, int userId) {
-                // Do nothing.
-            }
-        };
-    }
-
-    private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
-        return new IWeakEscrowTokenRemovedListener.Stub() {
-            @Override
-            public void onWeakEscrowTokenRemoved(long handle, int userId) {
-                // Do nothing.
-            }
-        };
-    }
-}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index c77004d..da91a96 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1867,6 +1867,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
     },
+    "-483957611": {
+      "message": "Resuming configuration dispatch for %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-481924678": {
       "message": "handleNotObscuredLocked w: %s, w.mHasSurface: %b, w.isOnScreen(): %b, w.isDisplayedLw(): %b, w.mAttrs.userActivityTimeout: %d",
       "level": "DEBUG",
@@ -4021,6 +4027,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "1473051122": {
+      "message": "Pausing configuration dispatch for  %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "1494644409": {
       "message": "  Rejecting as detached: %s",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index d023cea..1232baa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -1020,7 +1020,7 @@
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                                 "RecentsController.finishInner: no valid PiP leash;"
                                         + "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d",
-                                mPipTransaction.toString(), mPipTask.toString(), mPipTaskId);
+                                mPipTransaction, mPipTask, mPipTaskId);
                     } else {
                         t.show(pipLeash);
                         PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 5a74255..e6faa63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -93,7 +93,7 @@
 
         // On a smaller screen, don't require as much empty space on screen, as offscreen
         // drags will be restricted too much.
-        final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.taskId)
+        final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.displayId)
                 .getResources().getConfiguration().smallestScreenWidthDp >= 600
                 ? R.dimen.freeform_required_visible_empty_space_in_header :
                 R.dimen.small_screen_required_visible_empty_space_in_header;
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
index 47bff8d..0d18535 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
@@ -78,6 +78,14 @@
             uiAutomation.dropShellPermissionIdentity()
         }
 
+        override fun onProcessStarted(
+            pid: Int,
+            processUid: Int,
+            packageUid: Int,
+            packageName: String,
+            processName: String
+        ) {}
+
         override fun onForegroundActivitiesChanged(pid: Int, uid: Int, foreground: Boolean) {}
 
         override fun onForegroundServicesChanged(pid: Int, uid: Int, serviceTypes: Int) {}
diff --git a/media/java/android/media/LoudnessCodecController.java b/media/java/android/media/LoudnessCodecController.java
index b3e5c52..61c9131 100644
--- a/media/java/android/media/LoudnessCodecController.java
+++ b/media/java/android/media/LoudnessCodecController.java
@@ -32,12 +32,13 @@
 
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
 
 /**
  * Class for getting recommended loudness parameter updates for audio decoders as they are used
@@ -320,11 +321,6 @@
      * Stops any loudness updates and frees up the resources.
      */
     @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void release() {
-        close();
-    }
-
-    /** @hide */
     @Override
     public void close() {
         synchronized (mControllerLock) {
@@ -339,9 +335,12 @@
     }
 
     /** @hide */
-    /*package*/ Map<LoudnessCodecInfo, Set<MediaCodec>> getRegisteredMediaCodecs() {
+    /*package*/ void mediaCodecsConsume(
+            Consumer<Entry<LoudnessCodecInfo, Set<MediaCodec>>> consumer) {
         synchronized (mControllerLock) {
-            return mMediaCodecs;
+            for (Entry<LoudnessCodecInfo, Set<MediaCodec>> entry : mMediaCodecs.entrySet()) {
+                consumer.accept(entry);
+            }
         }
     }
 
diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java
index 46be54b..fa08658 100644
--- a/media/java/android/media/LoudnessCodecDispatcher.java
+++ b/media/java/android/media/LoudnessCodecDispatcher.java
@@ -32,7 +32,6 @@
 
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
@@ -81,16 +80,15 @@
                     mConfiguratorListener.computeIfPresent(listener, (l, lcConfig) -> {
                         // send the appropriate bundle for the user to update
                         if (lcConfig.getSessionId() == sessionId) {
-                            final Map<LoudnessCodecInfo, Set<MediaCodec>> mediaCodecsMap =
-                                    lcConfig.getRegisteredMediaCodecs();
-                            for (LoudnessCodecInfo codecInfo : mediaCodecsMap.keySet()) {
+                            lcConfig.mediaCodecsConsume(mcEntry -> {
+                                final LoudnessCodecInfo codecInfo = mcEntry.getKey();
                                 final String infoKey = Integer.toString(codecInfo.hashCode());
                                 Bundle bundle = null;
                                 if (params.containsKey(infoKey)) {
                                     bundle = new Bundle(params.getPersistableBundle(infoKey));
                                 }
 
-                                final Set<MediaCodec> mediaCodecs = mediaCodecsMap.get(codecInfo);
+                                final Set<MediaCodec> mediaCodecs = mcEntry.getValue();
                                 for (MediaCodec mediaCodec : mediaCodecs) {
                                     final String mediaCodecKey = Integer.toString(
                                             mediaCodec.hashCode());
@@ -121,7 +119,7 @@
                                         break;
                                     }
                                 }
-                            }
+                            });
                         }
                         return lcConfig;
                     });
diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
index 25040a9..e872a58 100644
--- a/media/jni/soundpool/android_media_SoundPool.cpp
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -86,7 +86,7 @@
     }
 
     // Retrieves the associated object, returns nullValue T if not available.
-    T get(JNIEnv *env, jobject thiz) {
+    T get(JNIEnv *env, jobject thiz) const {
         std::lock_guard lg(mLock);
         // NOLINTNEXTLINE(performance-no-int-to-ptr)
         auto ptr = reinterpret_cast<T*>(env->GetLongField(thiz, mFieldId));
@@ -167,8 +167,10 @@
 //    is possible by checking if the WeakGlobalRef is null equivalent.
 
 auto& getSoundPoolManager() {
-    static ObjectManager<std::shared_ptr<SoundPool>> soundPoolManager(fields.mNativeContext);
-    return soundPoolManager;
+    // never-delete singleton
+    static auto soundPoolManager =
+            new ObjectManager<std::shared_ptr<SoundPool>>(fields.mNativeContext);
+    return *soundPoolManager;
 }
 
 inline auto getSoundPool(JNIEnv *env, jobject thiz) {
@@ -274,8 +276,9 @@
 auto& getSoundPoolJavaRefManager() {
     // Note this can store shared_ptrs to either jweak and jobject,
     // as the underlying type is identical.
-    static ConcurrentHashMap<SoundPool *, std::shared_ptr<JWeakValue>> concurrentHashMap;
-    return concurrentHashMap;
+    static auto concurrentHashMap =
+            new ConcurrentHashMap<SoundPool *, std::shared_ptr<JWeakValue>>();
+    return *concurrentHashMap;
 }
 
 // make_shared_globalref_from_localref() creates a sharable Java global
diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
index 4f6ede5..46256ba 100644
--- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
+++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
@@ -126,7 +126,7 @@
 
         try {
             mLcc.addMediaCodec(mediaCodec);
-            mLcc.release();  // stops updats
+            mLcc.close();  // stops updates
 
             verify(mAudioService).stopLoudnessCodecUpdates(eq(mSessionId));
         } finally {
diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
index 5becc86..f13402c 100644
--- a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
@@ -23,7 +23,7 @@
         android:shape="rectangle"
         android:top="1dp">
         <shape>
-            <corners android:radius="16dp" />
+            <corners android:radius="4dp" />
             <solid android:color="@color/dropdown_container" />
         </shape>
     </item>
diff --git a/packages/CredentialManager/res/drawable/more_options_list_item.xml b/packages/CredentialManager/res/drawable/more_options_list_item.xml
new file mode 100644
index 0000000..d7b509e
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/more_options_list_item.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
+        android:color="@android:color/transparent">
+    <item
+        android:bottom="1dp"
+        android:shape="rectangle"
+        android:top="1dp">
+        <shape>
+            <corners android:bottomLeftRadius="4dp"
+                     android:bottomRightRadius="4dp"/>
+            <solid android:color="@color/sign_in_options_container" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
new file mode 100644
index 0000000..929756c
--- /dev/null
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -0,0 +1,42 @@
+<!--
+  ~ 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.
+  -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:id="@android:id/content"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
+                android:elevation="3dp">
+
+    <ImageView
+        android:id="@android:id/icon1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_alignParentStart="true"
+        android:contentDescription="@string/provider_icon_content_description"
+        android:background="@null"/>
+    <TextView
+        android:id="@android:id/text1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_toEndOf="@android:id/icon1"
+        android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+        android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
+        style="@style/autofill.TextTitle"/>
+
+</RelativeLayout>
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index cb6c6b4..1fe5e0e 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -17,22 +17,25 @@
                 android:id="@android:id/content"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:maxWidth="@dimen/autofill_dropdown_layout_width"
+                android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
                 android:elevation="3dp">
 
         <ImageView
             android:id="@android:id/icon1"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:contentDescription="@string/provider_icon_content_description"
             android:layout_centerVertical="true"
             android:layout_alignParentStart="true"
             android:background="@null"/>
         <TextView
             android:id="@android:id/text1"
-            android:layout_width="@dimen/autofill_dropdown_text_width"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_alignParentTop="true"
             android:layout_toEndOf="@android:id/icon1"
+            android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+            android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
             style="@style/autofill.TextTitle"/>
         <TextView
             android:id="@android:id/text2"
@@ -40,6 +43,8 @@
             android:layout_height="wrap_content"
             android:layout_below="@android:id/text1"
             android:layout_toEndOf="@android:id/icon1"
+            android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+            android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
             style="@style/autofill.TextSubtitle"/>
 
 </RelativeLayout>
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
index dcb7ef9..7cb1d01 100644
--- a/packages/CredentialManager/res/values/colors.xml
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -20,4 +20,6 @@
     <color name="text_primary">#1A1B20</color>
     <color name="text_secondary">#44474F</color>
     <color name="dropdown_container">#F3F3FA</color>
+    <color name="sign_in_options_container">#DADADA</color>
+    <color name="sign_in_options_icon_color">#1B1B1B</color>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 2a4719d..3a8c78f 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -18,11 +18,13 @@
 
 <resources>
     <dimen name="autofill_view_top_padding">12dp</dimen>
-    <dimen name="autofill_view_right_padding">24dp</dimen>
+    <dimen name="autofill_view_right_padding">12dp</dimen>
     <dimen name="autofill_view_bottom_padding">12dp</dimen>
     <dimen name="autofill_view_left_padding">16dp</dimen>
     <dimen name="autofill_view_icon_to_text_padding">10dp</dimen>
     <dimen name="autofill_icon_size">24dp</dimen>
-    <dimen name="autofill_dropdown_layout_width">296dp</dimen>
-    <dimen name="autofill_dropdown_text_width">240dp</dimen>
+    <dimen name="autofill_dropdown_textview_min_width">112dp</dimen>
+    <dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
+    <dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
+    <integer name="autofill_max_visible_datasets">3</integer>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 605e77b..f98164b 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -168,4 +168,9 @@
   <string name="get_dialog_option_headline_use_a_different_device">Use a different device</string>
   <!-- Text shown on a snackbar when the app cancelled the UI. [CHAR LIMIT=120] -->
   <string name="request_cancelled_by">Request cancelled by <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+
+  <!-- Strings for dropdown presentation. -->
+  <!-- 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="provider_icon_content_description">Credential provider icon</string>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 03ac605..985f322 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -30,6 +30,7 @@
 import android.os.Bundle
 import android.os.CancellationSignal
 import android.os.OutcomeReceiver
+import android.provider.Settings
 import android.credentials.Credential
 import android.service.autofill.AutofillService
 import android.service.autofill.Dataset
@@ -48,7 +49,9 @@
 import android.view.autofill.AutofillId
 import android.widget.inline.InlinePresentationSpec
 import android.credentials.CredentialManager
+import android.widget.RemoteViews
 import androidx.autofill.inline.v1.InlineSuggestionUi
+import androidx.core.content.ContextCompat
 import androidx.credentials.provider.CustomCredentialEntry
 import androidx.credentials.provider.PasswordCredentialEntry
 import androidx.credentials.provider.PublicKeyCredentialEntry
@@ -115,7 +118,7 @@
         }
 
         val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
-            requestId)
+                requestId)
         if (getCredRequest == null) {
             Log.i(TAG, "No credential manager request found")
             callback.onFailure("No credential manager request found")
@@ -307,10 +310,14 @@
         val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0
         val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
         val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
-        var maxItemCount = totalEntryCount
-        if (inlineMaxSuggestedCount > 0) {
-            maxItemCount = maxItemCount.coerceAtMost(inlineMaxSuggestedCount)
-        }
+        val maxDropdownDisplayLimit = this.resources.getInteger(
+                com.android.credentialmanager.R.integer.autofill_max_visible_datasets)
+        var maxInlineItemCount = totalEntryCount
+        maxInlineItemCount = maxInlineItemCount.coerceAtMost(inlineMaxSuggestedCount)
+        val lastDropdownDatasetIndex = Settings.Global.getInt(this.contentResolver,
+                Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
+                (maxDropdownDisplayLimit - 1).coerceAtMost(totalEntryCount - 1))
+
         var i = 0
         var datasetAdded = false
 
@@ -333,13 +340,8 @@
                 Log.e(TAG, "PendingIntent was missing from the entry.")
                 return@usernameLoop
             }
-            if (inlinePresentationSpecs == null) {
-                Log.i(TAG, "Inline presentation spec is null, " +
-                        "building dropdown presentation only")
-            }
-            if (i >= maxItemCount) {
-                Log.e(TAG, "Skipping because reached the max item count.")
-                return@usernameLoop
+            if (i >= maxInlineItemCount && i >= lastDropdownDatasetIndex) {
+                return@usernameLoop;
             }
             val icon: Icon = if (primaryEntry.icon == null) {
                 // The empty entry icon has non-null icon reference but null drawable reference.
@@ -351,38 +353,26 @@
             }
             // Create inline presentation
             var inlinePresentation: InlinePresentation? = null
-            var spec: InlinePresentationSpec?
-            if (inlinePresentationSpecs != null) {
-                if (i < inlinePresentationSpecsCount) {
-                    spec = inlinePresentationSpecs[i]
+            if (inlinePresentationSpecs != null && i < maxInlineItemCount) {
+                val spec: InlinePresentationSpec? = if (i < inlinePresentationSpecsCount) {
+                    inlinePresentationSpecs[i]
                 } else {
-                    spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+                    inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
                 }
-                val displayName: String = if (primaryEntry.credentialType ==
-                        CredentialType.PASSKEY && primaryEntry.displayName != null) {
-                    primaryEntry.displayName!!
-                } else {
-                    primaryEntry.userName
-                }
-                val sliceBuilder = InlineSuggestionUi
-                        .newContentBuilder(pendingIntent)
-                        .setTitle(displayName)
-                sliceBuilder.setStartIcon(icon)
-                if (primaryEntry.credentialType ==
-                        CredentialType.PASSKEY && duplicateDisplayNamesForPasskeys[displayName]
-                        == true) {
-                    sliceBuilder.setSubtitle(primaryEntry.userName)
-                }
-                inlinePresentation = InlinePresentation(
-                        sliceBuilder.build().slice, spec, /* pinned= */ false)
+                inlinePresentation = createInlinePresentation(primaryEntry, pendingIntent, icon,
+                        spec!!, duplicateDisplayNamesForPasskeys)
             }
-            val dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
-                    this, icon, primaryEntry)
-            i++
+            var dropdownPresentation: RemoteViews? = null
+            if (i < lastDropdownDatasetIndex) {
+                dropdownPresentation = RemoteViewsFactory
+                        .createDropdownPresentation(this, icon, primaryEntry)
+            }
 
             val dataSetBuilder = Dataset.Builder()
             val presentationBuilder = Presentations.Builder()
-                    .setMenuPresentation(dropdownPresentation)
+            if (dropdownPresentation != null) {
+                presentationBuilder.setMenuPresentation(dropdownPresentation)
+            }
             if (inlinePresentation != null) {
                 presentationBuilder.setInlinePresentation(inlinePresentation)
             }
@@ -398,6 +388,12 @@
                             .setAuthenticationExtras(fillInIntent.extras)
                             .build())
             datasetAdded = true
+            i++
+
+            if (i == lastDropdownDatasetIndex && bottomSheetPendingIntent != null) {
+                addDropdownMoreOptionsPresentation(bottomSheetPendingIntent, autofillId,
+                        fillResponseBuilder)
+            }
         }
         val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs,
                 inlinePresentationSpecsCount)
@@ -408,6 +404,49 @@
         return datasetAdded
     }
 
+    private fun createInlinePresentation(primaryEntry: CredentialEntryInfo,
+                                         pendingIntent: PendingIntent,
+                                         icon: Icon,
+                                         spec: InlinePresentationSpec,
+                                         duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>):
+            InlinePresentation {
+        val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY
+                && primaryEntry.displayName != null) {
+            primaryEntry.displayName!!
+        } else {
+            primaryEntry.userName
+        }
+        val sliceBuilder = InlineSuggestionUi
+                .newContentBuilder(pendingIntent)
+                .setTitle(displayName)
+        sliceBuilder.setStartIcon(icon)
+        if (primaryEntry.credentialType ==
+                CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
+            sliceBuilder.setSubtitle(primaryEntry.userName)
+        }
+        return InlinePresentation(
+                sliceBuilder.build().slice, spec, /* pinned= */ false)
+    }
+
+    private fun addDropdownMoreOptionsPresentation(
+            bottomSheetPendingIntent: PendingIntent,
+            autofillId: AutofillId,
+            fillResponseBuilder: FillResponse.Builder) {
+        val presentationBuilder = Presentations.Builder()
+                .setMenuPresentation(RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
+
+        fillResponseBuilder.addDataset(
+                Dataset.Builder()
+                        .setField(
+                                autofillId,
+                                Field.Builder().setPresentations(
+                                        presentationBuilder.build())
+                                        .build())
+                        .setAuthentication(bottomSheetPendingIntent.intentSender)
+                        .build()
+        )
+    }
+
     private fun getLastInlinePresentationSpec(
             inlinePresentationSpecs: List<InlinePresentationSpec>?,
             inlinePresentationSpecsCount: Int
@@ -534,9 +573,9 @@
     }
 
     private fun getCredManRequest(
-        structure: AssistStructure,
-        sessionId: Int,
-        requestId: Int
+            structure: AssistStructure,
+            sessionId: Int,
+            requestId: Int
     ): GetCredentialRequest? {
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
         traverseStructure(structure, credentialOptions)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index e039dea..68f1c86 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -44,7 +44,7 @@
             if (credentialEntryInfo.credentialType == CredentialType.UNKNOWN) {
                 return remoteViews
             }
-            setRemoteViewsPaddings(remoteViews, context)
+            setRemoteViewsPaddings(remoteViews, context, /* primaryTextBottomPadding=*/0)
             if (credentialEntryInfo.credentialType == CredentialType.PASSKEY) {
                 val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
                 remoteViews.setTextViewText(android.R.id.text1, displayName)
@@ -81,8 +81,46 @@
             return remoteViews
         }
 
+        fun createMoreSignInOptionsPresentation(context: Context): RemoteViews {
+            var layoutId: Int = com.android.credentialmanager.R.layout
+                    .credman_dropdown_bottom_sheet
+            val remoteViews = RemoteViews(context.packageName, layoutId)
+            setRemoteViewsPaddings(remoteViews, context)
+            remoteViews.setTextViewText(android.R.id.text1, ContextCompat.getString(context,
+                    com.android.credentialmanager
+                            .R.string.dropdown_presentation_more_sign_in_options_text))
+
+            val textColorPrimary = ContextCompat.getColor(context,
+                    com.android.credentialmanager.R.color.text_primary)
+            remoteViews.setTextColor(android.R.id.text1, textColorPrimary)
+            val icon = Icon.createWithResource(context, com
+                    .android.credentialmanager.R.drawable.more_horiz_24px)
+            icon.setTint(ContextCompat.getColor(context,
+                    com.android.credentialmanager.R.color.sign_in_options_icon_color))
+            remoteViews.setImageViewIcon(android.R.id.icon1, icon)
+            remoteViews.setBoolean(
+                    android.R.id.icon1, setAdjustViewBoundsMethodName, true);
+            remoteViews.setInt(
+                    android.R.id.icon1,
+                    setMaxHeightMethodName,
+                    context.resources.getDimensionPixelSize(
+                            com.android.credentialmanager.R.dimen.autofill_icon_size));
+            val drawableId =
+                    com.android.credentialmanager.R.drawable.more_options_list_item
+            remoteViews.setInt(
+                    android.R.id.content, setBackgroundResourceMethodName, drawableId);
+            return remoteViews
+        }
+
         private fun setRemoteViewsPaddings(
                 remoteViews: RemoteViews, context: Context) {
+            val bottomPadding = context.resources.getDimensionPixelSize(
+                    com.android.credentialmanager.R.dimen.autofill_view_bottom_padding)
+            setRemoteViewsPaddings(remoteViews, context, bottomPadding)
+        }
+
+        private fun setRemoteViewsPaddings(
+                remoteViews: RemoteViews, context: Context, primaryTextBottomPadding: Int) {
             val leftPadding = context.resources.getDimensionPixelSize(
                     com.android.credentialmanager.R.dimen.autofill_view_left_padding)
             val iconToTextPadding = context.resources.getDimensionPixelSize(
@@ -104,7 +142,7 @@
                     iconToTextPadding,
                     /* top=*/topPadding,
                     /* right=*/rightPadding,
-                    /* bottom=*/0)
+                    primaryTextBottomPadding)
             remoteViews.setViewPadding(
                     android.R.id.text2,
                     iconToTextPadding,
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index cd5f597..b015b2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -234,6 +234,6 @@
     EditUserPhotoController createEditUserPhotoController(Activity activity,
             ActivityStarter activityStarter, ImageView userPhotoView) {
         return new EditUserPhotoController(activity, activityStarter, userPhotoView,
-                mSavedPhoto, mSavedDrawable, mFileAuthority);
+                mSavedPhoto, mSavedDrawable, mFileAuthority, false);
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index e83b9bc..b2de5a9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -62,6 +62,7 @@
 
     private static final String AVATAR_PICKER_ACTION = "com.android.avatarpicker"
             + ".FULL_SCREEN_ACTIVITY";
+    static final String EXTRA_IS_USER_NEW = "is_user_new";
 
     private final Activity mActivity;
     private final ActivityStarter mActivityStarter;
@@ -72,9 +73,13 @@
     private Bitmap mNewUserPhotoBitmap;
     private Drawable mNewUserPhotoDrawable;
     private String mCachedDrawablePath;
-
     public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
             ImageView view, Bitmap savedBitmap, Drawable savedDrawable, String fileAuthority) {
+        this(activity, activityStarter, view, savedBitmap, savedDrawable, fileAuthority, true);
+    }
+    public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
+            ImageView view, Bitmap savedBitmap, Drawable savedDrawable, String fileAuthority,
+            boolean isUserNew) {
         mActivity = activity;
         mActivityStarter = activityStarter;
         mFileAuthority = fileAuthority;
@@ -82,7 +87,7 @@
         mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
         mImagesDir.mkdir();
         mImageView = view;
-        mImageView.setOnClickListener(v -> showAvatarPicker());
+        mImageView.setOnClickListener(v -> showAvatarPicker(isUserNew));
 
         mNewUserPhotoBitmap = savedBitmap;
         mNewUserPhotoDrawable = savedDrawable;
@@ -117,11 +122,12 @@
         return mNewUserPhotoDrawable;
     }
 
-    private void showAvatarPicker() {
+    private void showAvatarPicker(boolean isUserNew) {
         Intent intent;
         if (Flags.avatarSync()) {
             intent = new Intent(AVATAR_PICKER_ACTION);
             intent.addCategory(Intent.CATEGORY_DEFAULT);
+            intent.putExtra(EXTRA_IS_USER_NEW, isUserNew);
         } else {
             intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
         }
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 2c35c77..7eca04a 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -364,3 +364,10 @@
    description: "Enables styled focus states on pin input field if keyboard is connected"
    bug: "316106516"
 }
+
+flag {
+   name: "enable_notif_linearlayout_optimized"
+   namespace: "systemui"
+   description: "Enables notification specific LinearLayout optimization"
+   bug: "316110233"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 409f15b..761e74e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -97,11 +97,13 @@
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.compose.ui.window.Popup
 import androidx.core.view.setPadding
+import com.android.compose.modifiers.thenIf
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
 import com.android.systemui.communal.ui.compose.extensions.allowGestures
+import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
 import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
 import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
@@ -132,6 +134,8 @@
     val removeButtonEnabled by remember {
         derivedStateOf { selectedIndex.value != null || reorderingWidgets }
     }
+    val (isButtonToEditWidgetsShowing, setIsButtonToEditWidgetsShowing) =
+        remember { mutableStateOf(false) }
 
     val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
     val contentOffset = beforeContentPadding(contentPadding).toOffset()
@@ -158,6 +162,11 @@
                             }
                         viewModel.setSelectedIndex(newIndex)
                     }
+                }
+                .thenIf(!viewModel.isEditMode) {
+                    Modifier.pointerInput(Unit) {
+                        detectLongPressGesture { offset -> setIsButtonToEditWidgetsShowing(true) }
+                    }
                 },
     ) {
         CommunalHubLazyGrid(
@@ -207,6 +216,16 @@
             PopupOnDismissCtaTile(viewModel::onHidePopupAfterDismissCta)
         }
 
+        if (isButtonToEditWidgetsShowing) {
+            ButtonToEditWidgets(
+                onClick = {
+                    setIsButtonToEditWidgetsShowing(false)
+                    viewModel.onOpenWidgetEditor()
+                },
+                onHide = { setIsButtonToEditWidgetsShowing(false) },
+            )
+        }
+
         // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving
         // touches, so that the SceneTransitionLayout can intercept the touches and allow an edge
         // swipe back to the blank scene.
@@ -414,6 +433,34 @@
 }
 
 @Composable
+private fun ButtonToEditWidgets(
+    onClick: () -> Unit,
+    onHide: () -> Unit,
+) {
+    Popup(alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onHide) {
+        val colors = LocalAndroidColorScheme.current
+        Button(
+            modifier =
+                Modifier.height(56.dp).background(colors.secondary, RoundedCornerShape(50.dp)),
+            onClick = onClick,
+        ) {
+            Icon(
+                imageVector = Icons.Outlined.Widgets,
+                contentDescription = stringResource(R.string.button_to_configure_widgets_text),
+                tint = colors.onSecondary,
+                modifier = Modifier.size(20.dp)
+            )
+            Spacer(modifier = Modifier.size(8.dp))
+            Text(
+                text = stringResource(R.string.button_to_configure_widgets_text),
+                style = MaterialTheme.typography.titleSmall,
+                color = colors.onSecondary,
+            )
+        }
+    }
+}
+
+@Composable
 private fun PopupOnDismissCtaTile(onHidePopupAfterDismissCta: () -> Unit) {
     Popup(
         alignment = Alignment.TopCenter,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
index 1407494..bc1e429 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
@@ -20,9 +20,13 @@
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.waitForUpOrCancellation
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
 import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.util.fastAny
+import androidx.compose.ui.util.fastForEach
 import kotlinx.coroutines.coroutineScope
 
 /**
@@ -44,6 +48,41 @@
     }
 }
 
+/**
+ * Detect long press gesture and calls onLongPress when detected. The callback parameter receives an
+ * Offset representing the position relative to the containing element.
+ */
+suspend fun PointerInputScope.detectLongPressGesture(
+    pass: PointerEventPass = PointerEventPass.Initial,
+    onLongPress: ((Offset) -> Unit),
+) = coroutineScope {
+    awaitEachGesture {
+        val down = awaitFirstDown(pass = pass)
+        val longPressTimeout = viewConfiguration.longPressTimeoutMillis
+        // wait for first tap up or long press
+        try {
+            withTimeout(longPressTimeout) { waitForUpOrCancellation(pass = pass) }
+        } catch (_: PointerEventTimeoutCancellationException) {
+            // withTimeout throws exception if timeout has passed before block completes
+            onLongPress.invoke(down.position)
+            consumeUntilUp(pass)
+        }
+    }
+}
+
+/**
+ * Consumes all pointer events until nothing is pressed and then returns. This method assumes that
+ * something is currently pressed.
+ */
+private suspend fun AwaitPointerEventScope.consumeUntilUp(
+    pass: PointerEventPass = PointerEventPass.Initial
+) {
+    do {
+        val event = awaitPointerEvent(pass = pass)
+        event.changes.fastForEach { it.consume() }
+    } while (event.changes.fastAny { it.pressed })
+}
+
 /** Consume all gestures on the initial pass so that child elements do not receive them. */
 suspend fun PointerInputScope.consumeAllGestures() = coroutineScope {
     awaitEachGesture {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 56d6879..bf02d8a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -173,7 +173,8 @@
                 val belowLockIconPlaceable =
                     belowLockIconMeasurable.measure(
                         noMinConstraints.copy(
-                            maxHeight = constraints.maxHeight - lockIconBounds.bottom
+                            maxHeight =
+                                (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
                         )
                     )
                 val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index fdf1166..616a7b4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -16,19 +16,42 @@
 
 package com.android.systemui.keyguard.ui.composable.blueprint
 
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
+import com.android.systemui.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
+import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
+import com.android.systemui.keyguard.ui.composable.section.ClockSection
+import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
+import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
+import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.res.R
+import com.android.systemui.shade.LargeScreenHeaderHelper
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
+import java.util.Optional
 import javax.inject.Inject
 
 /**
@@ -39,22 +62,174 @@
 @Inject
 constructor(
     private val viewModel: LockscreenContentViewModel,
+    private val statusBarSection: StatusBarSection,
+    private val clockSection: ClockSection,
+    private val smartSpaceSection: SmartSpaceSection,
+    private val notificationSection: NotificationSection,
+    private val lockSection: LockSection,
+    private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
+    private val bottomAreaSection: BottomAreaSection,
+    private val settingsMenuSection: SettingsMenuSection,
+    private val clockInteractor: KeyguardClockInteractor,
+    private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
 ) : LockscreenSceneBlueprint {
 
     override val id: String = "split-shade"
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
+        val isUdfpsVisible = viewModel.isUdfpsVisible
+        val burnIn = rememberBurnIn(clockInteractor)
+        val resources = LocalContext.current.resources
+
         LockscreenLongPress(
             viewModel = viewModel.longPress,
             modifier = modifier,
-        ) { _ ->
-            Box(modifier.background(Color.Black)) {
-                Text(
-                    text = "TODO(b/316211368): split shade blueprint",
-                    color = Color.White,
-                    modifier = Modifier.align(Alignment.Center),
-                )
+        ) { onSettingsMenuPlaced ->
+            Layout(
+                content = {
+                    // Constrained to above the lock icon.
+                    Column(
+                        modifier = Modifier.fillMaxSize(),
+                    ) {
+                        with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+                        Row(
+                            modifier = Modifier.fillMaxSize(),
+                        ) {
+                            Column(
+                                modifier = Modifier.fillMaxHeight().weight(weight = 1f),
+                                horizontalAlignment = Alignment.CenterHorizontally,
+                            ) {
+                                with(smartSpaceSection) {
+                                    SmartSpace(
+                                        burnInParams = burnIn.parameters,
+                                        onTopChanged = burnIn.onSmartspaceTopChanged,
+                                        modifier =
+                                            Modifier.fillMaxWidth()
+                                                .padding(
+                                                    top = {
+                                                        viewModel.getSmartSpacePaddingTop(resources)
+                                                    }
+                                                ),
+                                    )
+                                }
+
+                                Spacer(modifier = Modifier.weight(weight = 1f))
+                                with(clockSection) { LargeClock() }
+                                Spacer(modifier = Modifier.weight(weight = 1f))
+                            }
+                            with(notificationSection) {
+                                val splitShadeTopMargin: Dp =
+                                    if (Flags.centralizedStatusBarDimensRefactor()) {
+                                        largeScreenHeaderHelper.getLargeScreenHeaderHeight().dp
+                                    } else {
+                                        dimensionResource(
+                                            id = R.dimen.large_screen_shade_header_height
+                                        )
+                                    }
+                                Notifications(
+                                    modifier =
+                                        Modifier.fillMaxHeight()
+                                            .weight(weight = 1f)
+                                            .padding(top = splitShadeTopMargin)
+                                )
+                            }
+                        }
+
+                        if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+                            with(ambientIndicationSectionOptional.get()) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
+                            }
+                        }
+                    }
+
+                    with(lockSection) { LockIcon() }
+
+                    // Aligned to bottom and constrained to below the lock icon.
+                    Column(modifier = Modifier.fillMaxWidth()) {
+                        if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+                            with(ambientIndicationSectionOptional.get()) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
+                            }
+                        }
+
+                        with(bottomAreaSection) {
+                            IndicationArea(modifier = Modifier.fillMaxWidth())
+                        }
+                    }
+
+                    // Aligned to bottom and NOT constrained by the lock icon.
+                    with(bottomAreaSection) {
+                        Shortcut(isStart = true, applyPadding = true)
+                        Shortcut(isStart = false, applyPadding = true)
+                    }
+                    with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+                },
+                modifier = Modifier.fillMaxSize(),
+            ) { measurables, constraints ->
+                check(measurables.size == 6)
+                val aboveLockIconMeasurable = measurables[0]
+                val lockIconMeasurable = measurables[1]
+                val belowLockIconMeasurable = measurables[2]
+                val startShortcutMeasurable = measurables[3]
+                val endShortcutMeasurable = measurables[4]
+                val settingsMenuMeasurable = measurables[5]
+
+                val noMinConstraints =
+                    constraints.copy(
+                        minWidth = 0,
+                        minHeight = 0,
+                    )
+                val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+                val lockIconBounds =
+                    IntRect(
+                        left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+                        top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+                        right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+                        bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+                    )
+
+                val aboveLockIconPlaceable =
+                    aboveLockIconMeasurable.measure(
+                        noMinConstraints.copy(maxHeight = lockIconBounds.top)
+                    )
+                val belowLockIconPlaceable =
+                    belowLockIconMeasurable.measure(
+                        noMinConstraints.copy(
+                            maxHeight =
+                                (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
+                        )
+                    )
+                val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
+                val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+                val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+                layout(constraints.maxWidth, constraints.maxHeight) {
+                    aboveLockIconPlaceable.place(
+                        x = 0,
+                        y = 0,
+                    )
+                    lockIconPlaceable.place(
+                        x = lockIconBounds.left,
+                        y = lockIconBounds.top,
+                    )
+                    belowLockIconPlaceable.place(
+                        x = 0,
+                        y = constraints.maxHeight - belowLockIconPlaceable.height,
+                    )
+                    startShortcutPleaceable.place(
+                        x = 0,
+                        y = constraints.maxHeight - startShortcutPleaceable.height,
+                    )
+                    endShortcutPleaceable.place(
+                        x = constraints.maxWidth - endShortcutPleaceable.width,
+                        y = constraints.maxHeight - endShortcutPleaceable.height,
+                    )
+                    settingsMenuPlaceable.place(
+                        x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+                        y = constraints.maxHeight - settingsMenuPlaceable.height,
+                    )
+                }
             }
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
index f40b871..8f21879 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
@@ -16,7 +16,8 @@
 
 package com.android.systemui.keyguard.ui.composable.section
 
-import androidx.compose.foundation.layout.fillMaxWidth
+import android.view.ViewGroup
+import android.widget.FrameLayout
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
@@ -75,7 +76,13 @@
         ) {
             content {
                 AndroidView(
-                    factory = { checkNotNull(currentClock).smallClock.view },
+                    factory = { context ->
+                        FrameLayout(context).apply {
+                            val newClockView = checkNotNull(currentClock).smallClock.view
+                            (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                            addView(newClockView)
+                        }
+                    },
                     modifier =
                         Modifier.padding(
                                 horizontal =
@@ -83,6 +90,12 @@
                             )
                             .padding(top = { viewModel.getSmallClockTopMargin(view.context) })
                             .onTopPlacementChanged(onTopChanged),
+                    update = {
+                        val newClockView = checkNotNull(currentClock).smallClock.view
+                        it.removeAllViews()
+                        (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                        it.addView(newClockView)
+                    },
                 )
             }
         }
@@ -116,8 +129,19 @@
         ) {
             content {
                 AndroidView(
-                    factory = { checkNotNull(currentClock).largeClock.view },
-                    modifier = Modifier.fillMaxWidth()
+                    factory = { context ->
+                        FrameLayout(context).apply {
+                            val newClockView = checkNotNull(currentClock).largeClock.view
+                            (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                            addView(newClockView)
+                        }
+                    },
+                    update = {
+                        val newClockView = checkNotNull(currentClock).largeClock.view
+                        it.removeAllViews()
+                        (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                        it.addView(newClockView)
+                    },
                 )
             }
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index 81d5344..bd9ca30 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -16,24 +16,28 @@
 
 package com.android.systemui.communal.data.repository
 
+import android.content.pm.UserInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.scene.data.repository.SceneContainerRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.data.repository.sceneContainerRepository
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -44,19 +48,23 @@
 class CommunalRepositoryImplTest : SysuiTestCase() {
     private lateinit var underTest: CommunalRepositoryImpl
 
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    private lateinit var secureSettings: FakeSettings
+    private lateinit var userRepository: FakeUserRepository
 
-    private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
-    private lateinit var sceneContainerRepository: SceneContainerRepository
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneContainerRepository = kosmos.sceneContainerRepository
 
     @Before
     fun setUp() {
-        val kosmos = testKosmos()
-        sceneContainerRepository = kosmos.sceneContainerRepository
-        featureFlagsClassic = FakeFeatureFlagsClassic()
+        secureSettings = FakeSettings()
+        userRepository = kosmos.fakeUserRepository
 
-        featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+        val listOfUserInfo = listOf(MAIN_USER_INFO)
+        userRepository.setUserInfos(listOfUserInfo)
+
+        kosmos.fakeFeatureFlagsClassic.apply { set(Flags.COMMUNAL_SERVICE_ENABLED, true) }
+        mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
 
         underTest = createRepositoryImpl(false)
     }
@@ -64,9 +72,13 @@
     private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl {
         return CommunalRepositoryImpl(
             testScope.backgroundScope,
-            featureFlagsClassic,
-            FakeSceneContainerFlags(enabled = sceneContainerEnabled),
+            testScope.backgroundScope,
+            kosmos.testDispatcher,
+            kosmos.fakeFeatureFlagsClassic,
+            kosmos.fakeSceneContainerFlags.apply { enabled = sceneContainerEnabled },
             sceneContainerRepository,
+            kosmos.fakeUserRepository,
+            secureSettings,
         )
     }
 
@@ -147,4 +159,29 @@
             assertThat(transitionState)
                 .isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT))
         }
+
+    @Test
+    fun communalEnabledState_false_whenGlanceableHubSettingFalse() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+            secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, MAIN_USER_INFO.id)
+
+            val communalEnabled by collectLastValue(underTest.communalEnabledState)
+            assertThat(communalEnabled).isFalse()
+        }
+
+    @Test
+    fun communalEnabledState_true_whenGlanceableHubSettingTrue() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+            secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, MAIN_USER_INFO.id)
+
+            val communalEnabled by collectLastValue(underTest.communalEnabledState)
+            assertThat(communalEnabled).isTrue()
+        }
+
+    companion object {
+        private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
+        private val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index f23716c..d5e43f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -25,10 +25,13 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
@@ -44,7 +47,6 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -56,7 +58,8 @@
 @RunWith(AndroidJUnit4::class)
 class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
     private val testDispatcher = StandardTestDispatcher()
     private val iStatusBarService = mock<IStatusBarService>()
     private val executor = FakeExecutor(FakeSystemClock())
@@ -79,6 +82,8 @@
                 headsUpManager,
                 powerInteractor,
                 activeNotificationsInteractor,
+                kosmos.sceneContainerFlags,
+                kosmos::sceneInteractor,
             )
             .apply { setUp(notificationPresenter, notificationsController) }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
new file mode 100644
index 0000000..51b8342
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeControllerSceneImplTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor = kosmos.sceneInteractor
+    private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+
+    private lateinit var shadeInteractor: ShadeInteractor
+    private lateinit var underTest: ShadeControllerSceneImpl
+
+    @Before
+    fun setup() {
+        kosmos.testCase = this
+        kosmos.fakeSceneContainerFlags.enabled = true
+        kosmos.fakeFeatureFlagsClassic.apply {
+            set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+            set(Flags.NSSL_DEBUG_LINES, false)
+            set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+        }
+        kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+        testScope.runCurrent()
+        shadeInteractor = kosmos.shadeInteractor
+        underTest = kosmos.shadeControllerSceneImpl
+    }
+
+    @Test
+    fun animateCollapseShade_noForceNoExpansion() =
+        testScope.runTest {
+            // GIVEN shade is collapsed and a post-collapse action is enqueued
+            val testRunnable = mock<Runnable>()
+            setDeviceEntered(true)
+            setCollapsed()
+            underTest.addPostCollapseAction(testRunnable)
+
+            // WHEN a collapse is requested
+            underTest.animateCollapseShade(0, force = false, delayed = false, 1f)
+            runCurrent()
+
+            // THEN the shade remains collapsed and the post-collapse action ran
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
+            verify(testRunnable, times(1)).run()
+        }
+
+    @Test
+    fun animateCollapseShade_expandedExcludeFlagOn() =
+        testScope.runTest {
+            // GIVEN shade is fully expanded and a post-collapse action is enqueued
+            val testRunnable = mock<Runnable>()
+            underTest.addPostCollapseAction(testRunnable)
+            setDeviceEntered(true)
+            setShadeFullyExpanded()
+
+            // WHEN a collapse is requested with FLAG_EXCLUDE_NOTIFICATION_PANEL
+            underTest.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL)
+            runCurrent()
+
+            // THEN the shade remains expanded and the post-collapse action did not run
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
+            assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue()
+            verify(testRunnable, never()).run()
+        }
+
+    @Test
+    fun animateCollapseShade_locked() =
+        testScope.runTest {
+            // GIVEN shade is fully expanded on lockscreen
+            setDeviceEntered(false)
+            setShadeFullyExpanded()
+
+            // WHEN a collapse is requested
+            underTest.animateCollapseShade()
+            runCurrent()
+
+            // THEN the shade collapses back to lockscreen and the post-collapse action ran
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun animateCollapseShade_unlocked() =
+        testScope.runTest {
+            // GIVEN shade is fully expanded on an unlocked device
+            setDeviceEntered(true)
+            setShadeFullyExpanded()
+
+            // WHEN a collapse is requested
+            underTest.animateCollapseShade()
+            runCurrent()
+
+            // THEN the shade collapses back to lockscreen and the post-collapse action ran
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun onCollapseShade_runPostCollapseActionsCalled() =
+        testScope.runTest {
+            // GIVEN shade is expanded and a post-collapse action is enqueued
+            val testRunnable = mock<Runnable>()
+            setShadeFullyExpanded()
+            underTest.addPostCollapseAction(testRunnable)
+
+            // WHEN shade collapses
+            setCollapsed()
+
+            // THEN post-collapse action ran
+            verify(testRunnable, times(1)).run()
+        }
+
+    @Test
+    fun postOnShadeExpanded() =
+        testScope.runTest {
+            // GIVEN shade is collapsed and a post-collapse action is enqueued
+            val testRunnable = mock<Runnable>()
+            setCollapsed()
+            underTest.postOnShadeExpanded(testRunnable)
+
+            // WHEN shade expands
+            setShadeFullyExpanded()
+
+            // THEN post-collapse action ran
+            verify(testRunnable, times(1)).run()
+        }
+
+    private fun setScene(key: SceneKey) {
+        sceneInteractor.changeScene(SceneModel(key), "test")
+        sceneInteractor.setTransitionState(
+            MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+        )
+        testScope.runCurrent()
+    }
+
+    private fun setDeviceEntered(isEntered: Boolean) {
+        setScene(
+            if (isEntered) {
+                SceneKey.Gone
+            } else {
+                SceneKey.Lockscreen
+            }
+        )
+        assertThat(deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered)
+    }
+
+    private fun setCollapsed() {
+        setScene(SceneKey.Gone)
+        assertThat(shadeInteractor.isAnyExpanded.value).isFalse()
+    }
+
+    private fun setShadeFullyExpanded() {
+        setScene(SceneKey.Shade)
+        assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
similarity index 69%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
index 0a10b2c..0c7ce97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
@@ -16,11 +16,10 @@
 
 package com.android.systemui.statusbar.notification.collection.render
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -34,18 +33,19 @@
 import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
+@RunWith(AndroidJUnit4::class)
 class GroupExpansionManagerTest : SysuiTestCase() {
-    private lateinit var gem: GroupExpansionManagerImpl
+    private lateinit var underTest: GroupExpansionManagerImpl
 
     private val dumpManager: DumpManager = mock()
     private val groupMembershipManager: GroupMembershipManager = mock()
-    private val featureFlags = FakeFeatureFlagsClassic()
 
     private val pipeline: NotifPipeline = mock()
     private lateinit var beforeRenderListListener: OnBeforeRenderListListener
@@ -85,79 +85,57 @@
         whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1)
         whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2)
 
-        gem = GroupExpansionManagerImpl(dumpManager, groupMembershipManager, featureFlags)
+        underTest = GroupExpansionManagerImpl(dumpManager, groupMembershipManager)
     }
 
     @Test
-    fun testNotifyOnlyOnChange_enabled() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
+    fun notifyOnlyOnChange() {
         var listenerCalledCount = 0
-        gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
+        underTest.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
 
-        gem.setGroupExpanded(summary1, false)
+        underTest.setGroupExpanded(summary1, false)
         assertThat(listenerCalledCount).isEqualTo(0)
-        gem.setGroupExpanded(summary1, true)
+        underTest.setGroupExpanded(summary1, true)
         assertThat(listenerCalledCount).isEqualTo(1)
-        gem.setGroupExpanded(summary2, true)
+        underTest.setGroupExpanded(summary2, true)
         assertThat(listenerCalledCount).isEqualTo(2)
-        gem.setGroupExpanded(summary1, true)
+        underTest.setGroupExpanded(summary1, true)
         assertThat(listenerCalledCount).isEqualTo(2)
-        gem.setGroupExpanded(summary2, false)
+        underTest.setGroupExpanded(summary2, false)
         assertThat(listenerCalledCount).isEqualTo(3)
     }
 
     @Test
-    fun testNotifyOnlyOnChange_disabled() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-
-        var listenerCalledCount = 0
-        gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
-
-        gem.setGroupExpanded(summary1, false)
-        assertThat(listenerCalledCount).isEqualTo(1)
-        gem.setGroupExpanded(summary1, true)
-        assertThat(listenerCalledCount).isEqualTo(2)
-        gem.setGroupExpanded(summary2, true)
-        assertThat(listenerCalledCount).isEqualTo(3)
-        gem.setGroupExpanded(summary1, true)
-        assertThat(listenerCalledCount).isEqualTo(4)
-        gem.setGroupExpanded(summary2, false)
-        assertThat(listenerCalledCount).isEqualTo(5)
-    }
-
-    @Test
-    fun testExpandUnattachedEntry() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
+    fun expandUnattachedEntry() {
         // First, expand the entry when it is attached.
-        gem.setGroupExpanded(summary1, true)
-        assertThat(gem.isGroupExpanded(summary1)).isTrue()
+        underTest.setGroupExpanded(summary1, true)
+        assertThat(underTest.isGroupExpanded(summary1)).isTrue()
 
         // Un-attach it, and un-expand it.
         NotificationEntryBuilder.setNewParent(summary1, null)
-        gem.setGroupExpanded(summary1, false)
+        underTest.setGroupExpanded(summary1, false)
 
         // Expanding again should throw.
-        assertThrows(IllegalArgumentException::class.java) { gem.setGroupExpanded(summary1, true) }
+        assertThrows(IllegalArgumentException::class.java) {
+            underTest.setGroupExpanded(summary1, true)
+        }
     }
 
     @Test
-    fun testSyncWithPipeline() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-        gem.attach(pipeline)
+    fun syncWithPipeline() {
+        underTest.attach(pipeline)
         beforeRenderListListener = withArgCaptor {
             verify(pipeline).addOnBeforeRenderListListener(capture())
         }
 
         val listener: OnGroupExpansionChangeListener = mock()
-        gem.registerGroupExpansionChangeListener(listener)
+        underTest.registerGroupExpansionChangeListener(listener)
 
         beforeRenderListListener.onBeforeRenderList(entries)
         verify(listener, never()).onGroupExpansionChange(any(), any())
 
         // Expand one of the groups.
-        gem.setGroupExpanded(summary1, true)
+        underTest.setGroupExpanded(summary1, true)
         verify(listener).onGroupExpansionChange(summary1.row, true)
 
         // Empty the pipeline list and verify that the group is no longer expanded.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
new file mode 100644
index 0000000..2cbcc5a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GroupMembershipManagerTest : SysuiTestCase() {
+    private var underTest = GroupMembershipManagerImpl()
+
+    @Test
+    fun isChildInGroup_topLevel() {
+        val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+        assertThat(underTest.isChildInGroup(topLevelEntry)).isFalse()
+    }
+
+    @Test
+    fun isChildInGroup_noParent() {
+        val noParentEntry = NotificationEntryBuilder().setParent(null).build()
+        assertThat(underTest.isChildInGroup(noParentEntry)).isFalse()
+    }
+
+    @Test
+    fun isChildInGroup_summary() {
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+        assertThat(underTest.isChildInGroup(summary)).isFalse()
+    }
+
+    @Test
+    fun isGroupSummary_topLevelEntry() {
+        val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+        assertThat(underTest.isGroupSummary(entry)).isFalse()
+    }
+
+    @Test
+    fun isGroupSummary_summary() {
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+        assertThat(underTest.isGroupSummary(summary)).isTrue()
+    }
+
+    @Test
+    fun isGroupSummary_child() {
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+        assertThat(underTest.isGroupSummary(entry)).isFalse()
+    }
+
+    @Test
+    fun getGroupSummary_topLevelEntry() {
+        val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+        assertThat(underTest.getGroupSummary(entry)).isNull()
+    }
+
+    @Test
+    fun getGroupSummary_summary() {
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+        assertThat(underTest.getGroupSummary(summary)).isEqualTo(summary)
+    }
+
+    @Test
+    fun getGroupSummary_child() {
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+        assertThat(underTest.getGroupSummary(entry)).isEqualTo(summary)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index 00a86ff..cc4ebd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -1,15 +1,17 @@
 /*
- * 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. You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package com.android.systemui.statusbar.phone
@@ -18,9 +20,9 @@
 import android.content.Intent
 import android.os.RemoteException
 import android.os.UserHandle
-import android.testing.AndroidTestingRunner
 import android.view.View
 import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.ActivityIntentHelper
@@ -66,7 +68,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class ActivityStarterImplTest : SysuiTestCase() {
     @Mock private lateinit var centralSurfaces: CentralSurfaces
     @Mock private lateinit var assistManager: AssistManager
@@ -139,7 +141,7 @@
     }
 
     @Test
-    fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLockscreen_activityLaunchAnimator() {
+    fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLs_launchAnimator() {
         val pendingIntent = mock(PendingIntent::class.java)
         val parent = FrameLayout(context)
         val view =
@@ -214,7 +216,7 @@
         mainExecutor.runAllReady()
 
         verify(deviceProvisionedController).isDeviceProvisioned
-        verify(shadeController).runPostCollapseRunnables()
+        verify(shadeController).collapseShadeForActivityStart()
     }
 
     @Test
@@ -226,7 +228,7 @@
         mainExecutor.runAllReady()
 
         verify(deviceProvisionedController).isDeviceProvisioned
-        verify(shadeController, never()).runPostCollapseRunnables()
+        verify(shadeController, never()).collapseShadeForActivityStart()
     }
 
     @Test
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index 2c7467d..fab7840 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -27,7 +27,7 @@
     tools:parentTag="com.android.systemui.privacy.OngoingPrivacyChip">
     >
 
-        <LinearLayout
+        <com.android.systemui.animation.view.LaunchableLinearLayout
             android:id="@+id/icons_container"
             android:layout_height="@dimen/ongoing_appops_chip_height"
             android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index dca84b9..b792acc 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -27,10 +27,11 @@
     android:fitsSystemWindows="true">
 
     <!-- Placeholder for the communal UI that will be replaced if the feature is enabled. -->
-    <ViewStub
+    <View
         android:id="@+id/communal_ui_stub"
         android:layout_width="match_parent"
-        android:layout_height="match_parent" />
+        android:layout_height="match_parent"
+        android:visibility="gone" />
 
     <com.android.systemui.scrim.ScrimView
         android:id="@+id/scrim_behind"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9bc7681..2b43360 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1089,6 +1089,8 @@
     <string name="cta_label_to_open_widget_picker">Add more widgets</string>
     <!-- Text for the popup to be displayed after dismissing the CTA tile. [CHAR LIMIT=50] -->
     <string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string>
+    <!-- Text for the button to configure widgets after long press. [CHAR LIMIT=50] -->
+    <string name="button_to_configure_widgets_text">Customize widgets</string>
     <!-- Label for the button which configures widgets [CHAR LIMIT=NONE] -->
     <string name="edit_widget">Edit widget</string>
     <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
index 259cca8..9e92c93 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
@@ -16,8 +16,11 @@
 
 package com.android.systemui.shared.system;
 
-import android.graphics.Matrix;
+import static android.os.Trace.TRACE_TAG_INPUT;
+
 import android.os.Looper;
+import android.os.Trace;
+import android.util.Log;
 import android.view.BatchedInputEventReceiver;
 import android.view.Choreographer;
 import android.view.InputChannel;
@@ -52,23 +55,24 @@
         return target.addBatch(src);
     }
 
-    /** @see MotionEvent#createRotateMatrix */
-    public static Matrix createRotationMatrix(
-            /*@Surface.Rotation*/ int rotation, int displayW, int displayH) {
-        return MotionEvent.createRotateMatrix(rotation, displayW, displayH);
-    }
-
     /**
      * @see BatchedInputEventReceiver
      */
     public static class InputEventReceiver {
 
+        private final String mName;
         private final BatchedInputEventReceiver mReceiver;
 
+        @Deprecated
         public InputEventReceiver(InputChannel inputChannel, Looper looper,
                 Choreographer choreographer, final InputEventListener listener) {
-            mReceiver = new BatchedInputEventReceiver(inputChannel, looper, choreographer) {
+            this("unknown", inputChannel, looper, choreographer, listener);
+        }
 
+        public InputEventReceiver(String name, InputChannel inputChannel, Looper looper,
+                Choreographer choreographer, final InputEventListener listener) {
+            mName = name;
+            mReceiver = new BatchedInputEventReceiver(inputChannel, looper, choreographer) {
                 @Override
                 public void onInputEvent(InputEvent event) {
                     listener.onInputEvent(event);
@@ -89,6 +93,9 @@
          */
         public void dispose() {
             mReceiver.dispose();
+            Trace.instant(TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " receiver disposed");
+            Log.d(InputMonitorCompat.TAG, "Input event receiver for monitor (" + mName
+                    + ") disposed");
         }
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
index c4aac11..78beaf7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
@@ -17,8 +17,13 @@
 
 import android.hardware.input.InputManagerGlobal;
 import android.os.Looper;
+import android.os.Trace;
+import android.util.Log;
 import android.view.Choreographer;
 import android.view.InputMonitor;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
 
 import com.android.systemui.shared.system.InputChannelCompat.InputEventListener;
 import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
@@ -27,14 +32,20 @@
  * @see android.view.InputMonitor
  */
 public class InputMonitorCompat {
+    static final String TAG = "InputMonitorCompat";
     private final InputMonitor mInputMonitor;
+    private final String mName;
 
     /**
      * Monitor input on the specified display for gestures.
      */
-    public InputMonitorCompat(String name, int displayId) {
+    public InputMonitorCompat(@NonNull String name, int displayId) {
+        mName = name + "-disp" + displayId;
         mInputMonitor = InputManagerGlobal.getInstance()
                 .monitorGestureInput(name, displayId);
+        Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " created");
+        Log.d(TAG, "Input monitor (" + mName + ") created");
+
     }
 
     /**
@@ -45,10 +56,19 @@
     }
 
     /**
+     * @see InputMonitor#getSurface()
+     */
+    public SurfaceControl getSurface() {
+        return mInputMonitor.getSurface();
+    }
+
+    /**
      * @see InputMonitor#dispose()
      */
     public void dispose() {
         mInputMonitor.dispose();
+        Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " disposed");
+        Log.d(TAG, "Input monitor (" + mName + ") disposed");
     }
 
     /**
@@ -56,7 +76,9 @@
      */
     public InputEventReceiver getInputReceiver(Looper looper, Choreographer choreographer,
             InputEventListener listener) {
-        return new InputEventReceiver(mInputMonitor.getInputChannel(), looper, choreographer,
+        Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " receiver created");
+        Log.d(TAG, "Input event receiver for monitor (" + mName + ") created");
+        return new InputEventReceiver(mName, mInputMonitor.getInputChannel(), looper, choreographer,
                 listener);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 3e8c6a7..536f3af 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -114,7 +114,6 @@
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.Dumpable;
-import com.android.systemui.Flags;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -383,7 +382,6 @@
     private List<SubscriptionInfo> mSubscriptionInfo;
     @VisibleForTesting
     protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
-    private boolean mFingerprintDetectRunning;
     private boolean mIsDreaming;
     private boolean mLogoutEnabled;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -1005,7 +1003,6 @@
         final boolean wasCancellingRestarting = mFingerprintRunningState
                 == BIOMETRIC_STATE_CANCELLING_RESTARTING;
         mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
-        mFingerprintDetectRunning = false;
         if (wasCancellingRestarting) {
             KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         } else {
@@ -1114,9 +1111,6 @@
         boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
         boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING;
         mFingerprintRunningState = fingerprintRunningState;
-        if (mFingerprintRunningState == BIOMETRIC_STATE_STOPPED) {
-            mFingerprintDetectRunning = false;
-        }
         mLogger.logFingerprintRunningState(mFingerprintRunningState);
         // Clients of KeyguardUpdateMonitor don't care about the internal state about the
         // asynchronousness of the cancel cycle. So only notify them if the actually running state
@@ -2105,7 +2099,6 @@
     @VisibleForTesting
     void resetBiometricListeningState() {
         mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
-        mFingerprintDetectRunning = false;
     }
 
     @VisibleForTesting
@@ -2544,10 +2537,8 @@
             return;
         }
         final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported());
-        final boolean running = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
-        final boolean runningOrRestarting = running
+        final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING
                 || mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
-        final boolean runDetect = shouldRunFingerprintDetect();
         if (runningOrRestarting && !shouldListenForFingerprint) {
             if (action == BIOMETRIC_ACTION_START) {
                 mLogger.v("Ignoring stopListeningForFingerprint()");
@@ -2559,24 +2550,10 @@
                 mLogger.v("Ignoring startListeningForFingerprint()");
                 return;
             }
-            startListeningForFingerprint(runDetect);
-        } else if (running && runDetect && !mFingerprintDetectRunning) {
-            if (action == BIOMETRIC_ACTION_STOP) {
-                mLogger.v("Ignoring startListeningForFingerprint(detect)");
-                return;
-            }
-            // stop running authentication and start running fingerprint detection
-            stopListeningForFingerprint();
-            startListeningForFingerprint(true);
+            startListeningForFingerprint();
         }
     }
 
-    private boolean shouldRunFingerprintDetect() {
-        return !isUnlockingWithFingerprintAllowed()
-                || (Flags.runFingerprintDetectOnDismissibleKeyguard()
-                && getUserCanSkipBouncer(mSelectedUserInteractor.getSelectedUserId()));
-    }
-
     /**
      * If a user is encrypted or not.
      * This is NOT related to the lock screen being visible or not.
@@ -2832,6 +2809,7 @@
                         && biometricEnabledForUser
                         && !isUserInLockdown(user);
         final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
+        final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
         final boolean shouldListenBouncerState =
                 !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
 
@@ -2894,7 +2872,7 @@
         }
     }
 
-    private void startListeningForFingerprint(boolean runDetect) {
+    private void startListeningForFingerprint() {
         final int userId = mSelectedUserInteractor.getSelectedUserId();
         final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
         if (mFingerprintCancelSignal != null) {
@@ -2924,20 +2902,18 @@
                         mFingerprintInteractiveToAuthProvider.getVendorExtension(userId));
             }
 
-            if (runDetect) {
+            if (!isUnlockingWithFingerprintAllowed()) {
                 mLogger.v("startListeningForFingerprint - detect");
                 mFpm.detectFingerprint(
                         mFingerprintCancelSignal,
                         mFingerprintDetectionCallback,
                         fingerprintAuthenticateOptions);
-                mFingerprintDetectRunning = true;
             } else {
                 mLogger.v("startListeningForFingerprint");
                 mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
                         mFingerprintAuthenticationCallback,
                         null /* handler */,
                         fingerprintAuthenticateOptions);
-                mFingerprintDetectRunning = false;
             }
             setFingerprintRunningState(BIOMETRIC_STATE_RUNNING);
         }
@@ -3962,7 +3938,6 @@
                 mSelectedUserInteractor.getSelectedUserId()));
         pw.println("  getUserUnlockedWithBiometric()="
                 + getUserUnlockedWithBiometric(mSelectedUserInteractor.getSelectedUserId()));
-        pw.println("  mFingerprintDetectRunning=" + mFingerprintDetectRunning);
         pw.println("  SIM States:");
         for (SimData data : mSimDatas.values()) {
             pw.println("    " + data.toString());
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index 0f5f869..4372826 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -20,11 +20,12 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.pm.UserInfo;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.UserHandle;
 
 import androidx.annotation.NonNull;
 
-import com.android.systemui.res.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.GuestResetOrExitSessionReceiver.ResetSessionDialogFactory;
@@ -32,6 +33,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.QSUserSwitcherEvent;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -61,6 +63,7 @@
     private final SecureSettings mSecureSettings;
     private final ResetSessionDialogFactory mResetSessionDialogFactory;
     private final GuestSessionNotification mGuestSessionNotification;
+    private final HandlerThread mHandlerThread;
 
     @VisibleForTesting
     public final UserTracker.Callback mUserChangedCallback =
@@ -111,13 +114,16 @@
         mSecureSettings = secureSettings;
         mGuestSessionNotification = guestSessionNotification;
         mResetSessionDialogFactory = resetSessionDialogFactory;
+        mHandlerThread = new HandlerThread("GuestResumeSessionReceiver");
+        mHandlerThread.start();
     }
 
     /**
      * Register this receiver with the {@link BroadcastDispatcher}
      */
     public void register() {
-        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+        mUserTracker.addCallback(mUserChangedCallback,
+                  new HandlerExecutor(mHandlerThread.getThreadHandler()));
     }
 
     private void cancelDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 86f372a..71d0e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -35,7 +35,6 @@
 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -51,7 +50,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.ViewController
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import java.io.PrintWriter
 import javax.inject.Inject
 import javax.inject.Provider
@@ -64,7 +62,6 @@
  *
  * The ripple uses the accent color of the current theme.
  */
-@ExperimentalCoroutinesApi
 @SysUISingleton
 class AuthRippleController @Inject constructor(
     private val sysuiContext: Context,
@@ -316,18 +313,6 @@
                 mView.fadeDwellRipple()
             }
         }
-
-        override fun onBiometricDetected(
-                userId: Int,
-                biometricSourceType: BiometricSourceType,
-                isStrongBiometric: Boolean
-        ) {
-            // TODO (b/309804148): add support detect auth ripple for deviceEntryUdfpsRefactor
-            if (!DeviceEntryUdfpsRefactor.isEnabled &&
-                    keyguardUpdateMonitor.getUserCanSkipBouncer(userId)) {
-                showUnlockRipple(biometricSourceType)
-            }
-        }
     }
 
     private val configurationChangedListener =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
index ad2136a..d28dbc0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -94,6 +94,10 @@
                         override fun onAuthenticationStopped() {
                             updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
                         }
+
+                        override fun onAuthenticationSucceeded(requestReason: Int, userId: Int) {}
+
+                        override fun onAuthenticationFailed(requestReason: Int, userId: Int) {}
                     }
 
                 updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 1f4be40..addd880 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -20,13 +20,18 @@
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.scene.data.repository.SceneContainerRepository
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -34,16 +39,26 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
 
 /** Encapsulates the state of communal mode. */
 interface CommunalRepository {
     /** Whether communal features are enabled. */
     val isCommunalEnabled: Boolean
 
+    /**
+     * A {@link StateFlow} that tracks whether communal hub is enabled (it can be disabled in
+     * settings).
+     */
+    val communalEnabledState: StateFlow<Boolean>
+
     /** Whether the communal hub is showing. */
     val isCommunalHubShowing: Flow<Boolean>
 
@@ -72,13 +87,36 @@
 class CommunalRepositoryImpl
 @Inject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
     @Background backgroundScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val featureFlagsClassic: FeatureFlagsClassic,
     sceneContainerFlags: SceneContainerFlags,
     sceneContainerRepository: SceneContainerRepository,
+    userRepository: UserRepository,
+    private val secureSettings: SecureSettings
 ) : CommunalRepository {
+
+    private val communalEnabledSettingState: Flow<Boolean> =
+        userRepository.selectedUserInfo
+            .flatMapLatest { userInfo -> observeSettings(userInfo.id) }
+            .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed())
+
+    override val communalEnabledState: StateFlow<Boolean> =
+        if (featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()) {
+            communalEnabledSettingState
+                .filterNotNull()
+                .stateIn(
+                    scope = applicationScope,
+                    started = SharingStarted.Eagerly,
+                    initialValue = true
+                )
+        } else {
+            MutableStateFlow(false)
+        }
+
     override val isCommunalEnabled: Boolean
-        get() = featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
+        get() = communalEnabledState.value
 
     private val _desiredScene: MutableStateFlow<CommunalSceneKey> =
         MutableStateFlow(CommunalSceneKey.DEFAULT)
@@ -115,4 +153,26 @@
         } else {
             desiredScene.map { sceneKey -> sceneKey == CommunalSceneKey.Communal }
         }
+
+    private fun observeSettings(userId: Int): Flow<Boolean> =
+        secureSettings
+            .observerFlow(
+                userId = userId,
+                names =
+                    arrayOf(
+                        GLANCEABLE_HUB_ENABLED,
+                    )
+            )
+            // Force an update
+            .onStart { emit(Unit) }
+            .map { readFromSettings(userId) }
+
+    private suspend fun readFromSettings(userId: Int): Boolean =
+        withContext(backgroundDispatcher) {
+            secureSettings.getIntForUser(GLANCEABLE_HUB_ENABLED, 1, userId) == 1
+        }
+
+    companion object {
+        private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 4c5871d..c36f7fa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -72,7 +72,12 @@
     val isCommunalEnabled: Boolean
         get() = communalRepository.isCommunalEnabled
 
-    val isCommunalAvailable =
+    /** A {@link StateFlow} that tracks whether communal features are enabled. */
+    val communalEnabledState: StateFlow<Boolean>
+        get() = communalRepository.communalEnabledState
+
+    /** Whether communal features are enabled and available. */
+    val isCommunalAvailable: StateFlow<Boolean> =
         flowOf(isCommunalEnabled)
             .flatMapLatest { enabled ->
                 if (enabled)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 3f7c152..d2883cc 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -44,11 +44,11 @@
     // 100 - notification
     // TODO(b/297792660): Tracking Bug
     @JvmField val UNCLEARED_TRANSIENT_HUN_FIX =
-        unreleasedFlag("uncleared_transient_hun_fix", teamfood = true)
+        releasedFlag("uncleared_transient_hun_fix")
 
     // TODO(b/298308067): Tracking Bug
     @JvmField val SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX =
-        unreleasedFlag("swipe_uncleared_transient_view_fix", teamfood = true)
+        releasedFlag("swipe_uncleared_transient_view_fix")
 
     // TODO(b/254512751): Tracking Bug
     val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
@@ -102,12 +102,6 @@
             default = true
         )
 
-    /** Only notify group expansion listeners when a change happens. */
-    // TODO(b/292213543): Tracking Bug
-    @JvmField
-    val NOTIFICATION_GROUP_EXPANSION_CHANGE =
-            releasedFlag("notification_group_expansion_change")
-
     // TODO(b/301955929)
     @JvmField
     val NOTIF_LS_BACKGROUND_THREAD =
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 3de9e68..a95ddb5 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -2437,6 +2437,7 @@
                     return true;
                 }
             });
+            mGlobalActionsLayout.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
             mGlobalActionsLayout.setRotationListener(this::onRotate);
             mGlobalActionsLayout.setAdapter(mAdapter);
             mContainer = findViewById(com.android.systemui.res.R.id.global_actions_container);
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index 23ee00d..a3029b2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -146,7 +146,7 @@
                 null,
                 UserHandle.ALL
             )
-            userTracker.addCallback(userTrackerCallback, mainExecutor)
+            userTracker.addCallback(userTrackerCallback, backgroundExecutor)
             loadSavedComponents()
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 58e0428..91c86df 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -84,6 +84,7 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -261,7 +262,7 @@
     private boolean mIsTrackpadThreeFingerSwipe;
     private boolean mIsButtonForcedVisible;
 
-    private InputMonitor mInputMonitor;
+    private InputMonitorCompat mInputMonitor;
     private InputChannelCompat.InputEventReceiver mInputEventReceiver;
 
     private NavigationEdgeBackPlugin mEdgeBackPlugin;
@@ -665,10 +666,8 @@
                 }
 
                 // Register input event receiver
-                mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
-                        "edge-swipe", mDisplayId);
-                mInputEventReceiver = new InputChannelCompat.InputEventReceiver(
-                        mInputMonitor.getInputChannel(), Looper.getMainLooper(),
+                mInputMonitor = new InputMonitorCompat("edge-swipe", mDisplayId);
+                mInputEventReceiver = mInputMonitor.getInputReceiver(Looper.getMainLooper(),
                         Choreographer.getInstance(), this::onInputEvent);
 
                 // Add a nav bar panel window
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 958ace35..21de185 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -26,6 +26,8 @@
 import android.database.ContentObserver;
 import android.os.BatteryManager;
 import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.IThermalEventListener;
 import android.os.IThermalService;
 import android.os.PowerManager;
@@ -95,6 +97,7 @@
     private Future mLastShowWarningTask;
     private boolean mEnableSkinTemperatureWarning;
     private boolean mEnableUsbTemperatureAlarm;
+    private final HandlerThread mHandlerThread;
 
     private int mLowBatteryAlertCloseLevel;
     private final int[] mLowBatteryReminderLevels = new int[2];
@@ -167,6 +170,8 @@
         mPowerManager = powerManager;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mUserTracker = userTracker;
+        mHandlerThread = new HandlerThread("PowerUI");
+        mHandlerThread.start();
     }
 
     public void start() {
@@ -185,7 +190,8 @@
                 false, obs, UserHandle.USER_ALL);
         updateBatteryWarningLevels();
         mReceiver.init();
-        mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+        mUserTracker.addCallback(mUserChangedCallback,
+                    new HandlerExecutor(mHandlerThread.getThreadHandler()));
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
 
         // Check to see if we need to let the user know that the phone previously shut down due
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 8e1b00d..7a4be3f 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -23,11 +23,11 @@
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
 import com.android.settingslib.Utils
 import com.android.systemui.res.R
-import com.android.systemui.animation.view.LaunchableFrameLayout
 import com.android.systemui.statusbar.events.BackgroundAnimatableView
 
 class OngoingPrivacyChip @JvmOverloads constructor(
@@ -35,7 +35,7 @@
     attrs: AttributeSet? = null,
     defStyleAttrs: Int = 0,
     defStyleRes: Int = 0
-) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
+) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
 
     private var configuration: Configuration
     private var iconMargin = 0
@@ -43,6 +43,8 @@
     private var iconColor = 0
 
     private val iconsContainer: LinearLayout
+    val launchableContentView
+        get() = iconsContainer
 
     var privacyList = emptyList<PrivacyItem>()
         set(value) {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
index 76ef8a2..f121630 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
@@ -26,9 +26,9 @@
 import android.os.UserHandle
 import android.permission.PermissionGroupUsage
 import android.permission.PermissionManager
-import android.view.View
 import androidx.annotation.MainThread
 import androidx.annotation.WorkerThread
+import androidx.core.view.isVisible
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.appops.AppOpsController
@@ -214,7 +214,7 @@
      * @param context A context to use to create the dialog.
      * @see filterAndSelect
      */
-    fun showDialog(context: Context, view: View? = null) {
+    fun showDialog(context: Context, privacyChip: OngoingPrivacyChip? = null) {
         dismissDialog()
         backgroundExecutor.execute {
             val usage = permGroupUsage()
@@ -277,8 +277,8 @@
                         )
                     d.setShowForAllUsers(true)
                     d.addOnDismissListener(onDialogDismissed)
-                    if (view != null) {
-                        val controller = getPrivacyDialogController(view)
+                    if (privacyChip != null) {
+                        val controller = getPrivacyDialogController(privacyChip)
                         if (controller == null) {
                             d.show()
                         } else {
@@ -296,10 +296,13 @@
         }
     }
 
-    private fun getPrivacyDialogController(source: View): DialogLaunchAnimator.Controller? {
-        val delegate = DialogLaunchAnimator.Controller.fromView(source) ?: return null
+    private fun getPrivacyDialogController(
+        source: OngoingPrivacyChip
+    ): DialogLaunchAnimator.Controller? {
+        val delegate =
+            DialogLaunchAnimator.Controller.fromView(source.launchableContentView) ?: return null
         return object : DialogLaunchAnimator.Controller by delegate {
-            override fun shouldAnimateExit() = false
+            override fun shouldAnimateExit() = source.isVisible
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 592cb3b..211b4594 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -192,6 +192,7 @@
     private DialogLaunchAnimator mDialogLaunchAnimator;
     private boolean mHasWifiEntries;
     private WifiStateWorker mWifiStateWorker;
+    private boolean mHasActiveSubId;
 
     @VisibleForTesting
     static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
@@ -299,6 +300,7 @@
                 mExecutor);
         // Listen the subscription changes
         mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener();
+        refreshHasActiveSubId();
         mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
                 mOnSubscriptionsChangedListener);
         mDefaultDataSubId = getDefaultDataSubscriptionId();
@@ -901,18 +903,22 @@
      * @return whether there is the carrier item in the slice.
      */
     boolean hasActiveSubId() {
-        if (mSubscriptionManager == null) {
-            if (DEBUG) {
-                Log.d(TAG, "SubscriptionManager is null, can not check carrier.");
-            }
+        if (isAirplaneModeEnabled() || mTelephonyManager == null) {
             return false;
         }
 
-        if (isAirplaneModeEnabled() || mTelephonyManager == null
-                || mSubscriptionManager.getActiveSubscriptionIdList().length <= 0) {
-            return false;
+        return mHasActiveSubId;
+    }
+
+    private void refreshHasActiveSubId() {
+        if (mSubscriptionManager == null) {
+            mHasActiveSubId = false;
+            Log.e(TAG, "SubscriptionManager is null, set mHasActiveSubId = false");
+            return;
         }
-        return true;
+
+        mHasActiveSubId = mSubscriptionManager.getActiveSubscriptionIdList().length > 0;
+        Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubId);
     }
 
     /**
@@ -1204,6 +1210,7 @@
 
         @Override
         public void onSubscriptionsChanged() {
+            refreshHasActiveSubId();
             updateListener();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index e2959fe..1c37908 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -23,16 +23,22 @@
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.init.NotificationsController
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import javax.inject.Inject
+import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
@@ -47,6 +53,8 @@
     private val headsUpManager: HeadsUpManager,
     private val powerInteractor: PowerInteractor,
     private val activeNotificationsInteractor: ActiveNotificationsInteractor,
+    sceneContainerFlags: SceneContainerFlags,
+    sceneInteractorProvider: Provider<SceneInteractor>,
 ) : CoreStartable {
 
     private var notificationPresenter: NotificationPresenter? = null
@@ -58,11 +66,28 @@
     /**
      * True if lockscreen (including AOD) or the shade is visible and false otherwise. Notably,
      * false if the bouncer is visible.
-     *
-     * TODO(b/297080059): Use [SceneInteractor] as the source of truth if the scene flag is on.
      */
     val isLockscreenOrShadeVisible: StateFlow<Boolean> =
-        windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
+        if (!sceneContainerFlags.isEnabled()) {
+            windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
+        } else {
+            sceneInteractorProvider
+                .get()
+                .transitionState
+                .map { state ->
+                    when (state) {
+                        is ObservableTransitionState.Idle ->
+                            state.scene == SceneKey.Shade || state.scene == SceneKey.Lockscreen
+                        is ObservableTransitionState.Transition ->
+                            state.toScene == SceneKey.Shade ||
+                                state.toScene == SceneKey.Lockscreen ||
+                                state.fromScene == SceneKey.Shade ||
+                                state.fromScene == SceneKey.Lockscreen
+                    }
+                }
+                .distinctUntilChanged()
+                .stateIn(scope, SharingStarted.Eagerly, false)
+        }
 
     /**
      * True if lockscreen (including AOD) or the shade is visible **and** the user is currently
diff --git a/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt
new file mode 100644
index 0000000..f71a401d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import dagger.Lazy
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** A base class for non-empty implementations of ShadeController. */
+@OptIn(ExperimentalCoroutinesApi::class)
+abstract class BaseShadeControllerImpl(
+    private val touchLog: LogBuffer,
+    protected val commandQueue: CommandQueue,
+    protected val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+    protected val notificationShadeWindowController: NotificationShadeWindowController,
+    protected val assistManagerLazy: Lazy<AssistManager>
+) : ShadeController {
+    protected lateinit var notifPresenter: NotificationPresenter
+    /** Runnables to run after completing a collapse of the shade. */
+    private val postCollapseActions = ArrayList<Runnable>()
+
+    override fun start() {
+        logTouchesTo(touchLog)
+    }
+
+    final override fun animateExpandShade() {
+        if (isShadeEnabled) {
+            expandToNotifications()
+        }
+    }
+
+    /** Expand the shade with notifications visible. */
+    protected abstract fun expandToNotifications()
+
+    final override fun animateExpandQs() {
+        if (isShadeEnabled) {
+            expandToQs()
+        }
+    }
+
+    /** Expand the shade showing only quick settings. */
+    protected abstract fun expandToQs()
+
+    final override fun addPostCollapseAction(action: Runnable) {
+        postCollapseActions.add(action)
+    }
+
+    protected fun runPostCollapseActions() {
+        val clonedList: ArrayList<Runnable> = ArrayList(postCollapseActions)
+        postCollapseActions.clear()
+        for (r in clonedList) {
+            r.run()
+        }
+        statusBarKeyguardViewManager.readyForKeyguardDone()
+    }
+
+    final override fun onLaunchAnimationEnd(launchIsFullScreen: Boolean) {
+        if (!this.notifPresenter.isCollapsing()) {
+            onClosingFinished()
+        }
+        if (launchIsFullScreen) {
+            instantCollapseShade()
+        }
+    }
+    final override fun onLaunchAnimationCancelled(isLaunchForActivity: Boolean) {
+        if (
+            notifPresenter.isPresenterFullyCollapsed() &&
+                !notifPresenter.isCollapsing() &&
+                isLaunchForActivity
+        ) {
+            onClosingFinished()
+        } else {
+            collapseShade(true /* animate */)
+        }
+    }
+
+    protected fun onClosingFinished() {
+        runPostCollapseActions()
+        if (!this.notifPresenter.isPresenterFullyCollapsed()) {
+            // if we set it not to be focusable when collapsing, we have to undo it when we aborted
+            // the closing
+            notificationShadeWindowController.setNotificationShadeFocusable(true)
+        }
+    }
+
+    override fun setNotificationPresenter(presenter: NotificationPresenter) {
+        notifPresenter = presenter
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 44cb0d6..97ec3f9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -22,6 +22,7 @@
 import android.view.GestureDetector
 import android.view.MotionEvent
 import android.view.View
+import android.view.ViewGroup
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
@@ -33,6 +34,7 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.util.kotlin.collectFlow
 import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
 
 /**
  * Controller that's responsible for the glanceable hub container view and its touch handling.
@@ -49,7 +51,7 @@
     private val powerManager: PowerManager,
 ) {
     /** The container view for the hub. This will not be initialized until [initView] is called. */
-    private lateinit var communalContainerView: View
+    private var communalContainerView: View? = null
 
     /**
      * The width of the area in which a right edge swipe can open the hub, in pixels. Read from
@@ -108,6 +110,11 @@
         return communalInteractor.isCommunalEnabled && isComposeAvailable()
     }
 
+    /** Returns a {@link StateFlow} that tracks whether communal hub is enabled. */
+    fun enabledState(): StateFlow<Boolean> {
+        return communalInteractor.communalEnabledState
+    }
+
     /**
      * Creates the container view containing the glanceable hub UI.
      *
@@ -125,42 +132,44 @@
         if (!isEnabled()) {
             throw RuntimeException("Glanceable hub is not enabled")
         }
-        if (::communalContainerView.isInitialized) {
+        if (communalContainerView != null) {
             throw RuntimeException("Communal view has already been initialized")
         }
 
         communalContainerView = containerView
 
         rightEdgeSwipeRegionWidth =
-            communalContainerView.resources.getDimensionPixelSize(
+            containerView.resources.getDimensionPixelSize(
                 R.dimen.communal_right_edge_swipe_region_width
             )
         topEdgeSwipeRegionWidth =
-            communalContainerView.resources.getDimensionPixelSize(
+            containerView.resources.getDimensionPixelSize(
                 R.dimen.communal_top_edge_swipe_region_height
             )
         bottomEdgeSwipeRegionWidth =
-            communalContainerView.resources.getDimensionPixelSize(
+            containerView.resources.getDimensionPixelSize(
                 R.dimen.communal_bottom_edge_swipe_region_height
             )
 
         collectFlow(
-            communalContainerView,
+            containerView,
             keyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState),
             { anyBouncerShowing = it }
         )
-        collectFlow(
-            communalContainerView,
-            communalInteractor.isCommunalShowing,
-            { hubShowing = it }
-        )
-        collectFlow(
-            communalContainerView,
-            shadeInteractor.isAnyFullyExpanded,
-            { shadeShowing = it }
-        )
+        collectFlow(containerView, communalInteractor.isCommunalShowing, { hubShowing = it })
+        collectFlow(containerView, shadeInteractor.isAnyFullyExpanded, { shadeShowing = it })
 
-        return communalContainerView
+        communalContainerView = containerView
+
+        return containerView
+    }
+
+    /** Removes the container view from its parent. */
+    fun disposeView() {
+        communalContainerView?.let {
+            (it.parent as ViewGroup).removeView(it)
+            communalContainerView = null
+        }
     }
 
     /**
@@ -173,10 +182,10 @@
      * to be fully in control of its own touch handling.
      */
     fun onTouchEvent(ev: MotionEvent): Boolean {
-        if (!::communalContainerView.isInitialized) {
-            return false
-        }
+        return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false
+    }
 
+    private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean {
         val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN
         val isUp = ev.actionMasked == MotionEvent.ACTION_UP
         val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL
@@ -190,7 +199,7 @@
         if (hubShowing && isDown) {
             val y = ev.rawY
             val topSwipe: Boolean = y <= topEdgeSwipeRegionWidth
-            val bottomSwipe = y >= communalContainerView.height - bottomEdgeSwipeRegionWidth
+            val bottomSwipe = y >= view.height - bottomEdgeSwipeRegionWidth
 
             if (topSwipe || bottomSwipe) {
                 // Don't intercept touches at the top/bottom edge so that swipes can open the
@@ -200,7 +209,7 @@
 
             if (!hubOccluded) {
                 isTrackingHubTouch = true
-                dispatchTouchEvent(ev)
+                dispatchTouchEvent(view, ev)
                 // Return true regardless of dispatch result as some touches at the start of a
                 // gesture may return false from dispatchTouchEvent.
                 return true
@@ -209,7 +218,7 @@
             if (isUp || isCancel) {
                 isTrackingHubTouch = false
             }
-            dispatchTouchEvent(ev)
+            dispatchTouchEvent(view, ev)
             // Return true regardless of dispatch result as some touches at the start of a gesture
             // may return false from dispatchTouchEvent.
             return true
@@ -223,11 +232,10 @@
 
         if (!isTrackingOpenGesture && isDown) {
             val x = ev.rawX
-            val inOpeningSwipeRegion: Boolean =
-                x >= communalContainerView.width - rightEdgeSwipeRegionWidth
+            val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth
             if (inOpeningSwipeRegion && !hubOccluded) {
                 isTrackingOpenGesture = true
-                dispatchTouchEvent(ev)
+                dispatchTouchEvent(view, ev)
                 // Return true regardless of dispatch result as some touches at the start of a
                 // gesture may return false from dispatchTouchEvent.
                 return true
@@ -236,7 +244,7 @@
             if (isUp || isCancel) {
                 isTrackingOpenGesture = false
             }
-            dispatchTouchEvent(ev)
+            dispatchTouchEvent(view, ev)
             // Return true regardless of dispatch result as some touches at the start of a gesture
             // may return false from dispatchTouchEvent.
             return true
@@ -249,8 +257,8 @@
      * Dispatches the touch event to the communal container and sends a user activity event to reset
      * the screen timeout.
      */
-    private fun dispatchTouchEvent(ev: MotionEvent) {
-        communalContainerView.dispatchTouchEvent(ev)
+    private fun dispatchTouchEvent(view: View, ev: MotionEvent) {
+        view.dispatchTouchEvent(ev)
         powerManager.userActivity(
             SystemClock.uptimeMillis(),
             PowerManager.USER_ACTIVITY_EVENT_TOUCH,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index aeccf00..c0ceba3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2883,7 +2883,9 @@
     private void onTrackingStarted() {
         endClosing();
         mShadeRepository.setLegacyShadeTracking(true);
-        mTrackingStartedListener.onTrackingStarted();
+        if (mTrackingStartedListener != null) {
+            mTrackingStartedListener.onTrackingStarted();
+        }
         notifyExpandingStarted();
         updateExpansionAndVisibility();
         mScrimController.onTrackingStarted();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 863bb36..5ecc54b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -605,16 +605,21 @@
      * The layout lives in {@link R.id.communal_ui_stub}.
      */
     public void setupCommunalHubLayout() {
-        if (!mGlanceableHubContainerController.isEnabled()) {
-            return;
-        }
-
-        // Replace the placeholder view with the communal UI.
-        View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
-        int index = mView.indexOfChild(communalPlaceholder);
-        mView.removeView(communalPlaceholder);
-
-        mView.addView(mGlanceableHubContainerController.initView(mView.getContext()), index);
+        collectFlow(
+                mView,
+                mGlanceableHubContainerController.enabledState(),
+                isEnabled -> {
+                    if (isEnabled) {
+                        View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
+                        int index = mView.indexOfChild(communalPlaceholder);
+                        mView.addView(
+                                mGlanceableHubContainerController.initView(mView.getContext()),
+                                index);
+                    } else {
+                        mGlanceableHubContainerController.disposeView();
+                    }
+                }
+        );
     }
 
     private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index 2c4b0b9..ec4b23a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -33,10 +33,20 @@
  * {@link com.android.systemui.keyguard.KeyguardViewMediator} and others.
  */
 public interface ShadeController extends CoreStartable {
-    /** True if the shade UI is enabled on this particular Android variant and false otherwise. */
+    /**
+     * True if the shade UI is enabled on this particular Android variant and false otherwise.
+     *
+     * @deprecated use ShadeInteractor instead
+     */
+    @Deprecated
     boolean isShadeEnabled();
 
-    /** Make our window larger and the shade expanded */
+    /**
+     * Make our window larger and the shade expanded
+     *
+     * @deprecated will no longer be needed when keyguard is a sibling view to the shade
+     */
+    @Deprecated
     void instantExpandShade();
 
     /** Collapse the shade instantly with no animation. */
@@ -74,13 +84,28 @@
     /** Expand the shade with quick settings expanded with an animation. */
     void animateExpandQs();
 
-    /** Posts a request to collapse the shade. */
+    /**
+     * Posts a request to collapse the shade.
+     *
+     * @deprecated use #animateCollapseShade
+     */
+    @Deprecated
     void postAnimateCollapseShade();
 
-    /** Posts a request to force collapse the shade. */
+    /**
+     * Posts a request to force collapse the shade.
+     *
+     * @deprecated use #animateForceCollapseShade
+     */
+    @Deprecated
     void postAnimateForceCollapseShade();
 
-    /** Posts a request to expand the shade to quick settings. */
+    /**
+     * Posts a request to expand the shade to quick settings.
+     *
+     * @deprecated use #animateExpandQs
+     */
+    @Deprecated
     void postAnimateExpandQs();
 
     /** Cancels any ongoing expansion touch handling and collapses the shade. */
@@ -90,26 +115,29 @@
      * If the shade is not fully expanded, collapse it animated.
      *
      * @return Seems to always return false
+     * @deprecated use {@link #collapseShade()} instead
      */
+    @Deprecated
     boolean closeShadeIfOpen();
 
     /**
-     * Returns whether the shade state is the keyguard or not.
-     */
-    boolean isKeyguard();
-
-    /**
      * Returns whether the shade is currently open.
      * Even though in the current implementation shade is in expanded state on keyguard, this
      * method makes distinction between shade being truly open and plain keyguard state:
      * - if QS and notifications are visible on the screen, return true
      * - for any other state, including keyguard, return false
+     *
+     * @deprecated will be replaced by ShadeInteractor once scene container launches
      */
+    @Deprecated
     boolean isShadeFullyOpen();
 
     /**
      * Returns whether shade or QS are currently opening or collapsing.
+     *
+     * @deprecated will be replaced by ShadeInteractor once scene container launches
      */
+    @Deprecated
     boolean isExpandingOrCollapsing();
 
     /**
@@ -127,37 +155,67 @@
      */
     void addPostCollapseAction(Runnable action);
 
-    /** Run all of the runnables added by {@link #addPostCollapseAction}. */
-    void runPostCollapseRunnables();
-
     /**
      * Close the shade if it was open
      *
      * @return true if the shade was open, else false
      */
-    boolean collapseShade();
+    void collapseShade();
 
     /**
      * If animate is true, does the same as {@link #collapseShade()}. Otherwise, instantly collapse
      * the shade. Post collapse runnables will be executed
      *
      * @param animate true to animate the collapse, false for instantaneous collapse
+     * @deprecated call either #animateCollapseShade or #instantCollapseShade
      */
+    @Deprecated
     void collapseShade(boolean animate);
 
-    /** Calls #collapseShade if already on the main thread. If not, posts a call to it. */
+    /**
+     * Calls #collapseShade if already on the main thread. If not, posts a call to it.
+     * @deprecated call #collapseShade
+     */
+    @Deprecated
     void collapseOnMainThread();
 
-    /** Makes shade expanded but not visible. */
+    /**
+     *  If necessary, instantly collapses the shade for an activity start, otherwise runs the
+     *  post-collapse runnables. Instant collapse is ok here, because the purpose is to have the
+     *  shade collapsed when the user returns to SysUI from the launched activity.
+     */
+    void collapseShadeForActivityStart();
+
+    /**
+     * Makes shade expanded but not visible.
+     *
+     * @deprecated no longer needed once keyguard is a sibling view to the shade
+     */
+    @Deprecated
     void makeExpandedInvisible();
 
-    /** Makes shade expanded and visible. */
+    /**
+     * Makes shade expanded and visible.
+     *
+     * @deprecated no longer needed once keyguard is a sibling view to the shade
+     */
+    @Deprecated
     void makeExpandedVisible(boolean force);
 
-    /** Returns whether the shade is expanded and visible. */
+    /**
+     * Returns whether the shade is expanded and visible.
+     *
+     * @deprecated no longer needed once keyguard is a sibling view to the shade
+     */
+    @Deprecated
     boolean isExpandedVisible();
 
-    /** Handle status bar touch event. */
+    /**
+     * Handle status bar touch event.
+     *
+     * @deprecated only called by CentralSurfaces, which is being deleted
+     */
+    @Deprecated
     void onStatusBarTouch(MotionEvent event);
 
     /** Called when a launch animation was cancelled. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
index 82959ee..08a0c93 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
@@ -42,9 +42,6 @@
     override fun closeShadeIfOpen(): Boolean {
         return false
     }
-    override fun isKeyguard(): Boolean {
-        return false
-    }
     override fun isShadeFullyOpen(): Boolean {
         return false
     }
@@ -53,12 +50,10 @@
     }
     override fun postOnShadeExpanded(action: Runnable?) {}
     override fun addPostCollapseAction(action: Runnable?) {}
-    override fun runPostCollapseRunnables() {}
-    override fun collapseShade(): Boolean {
-        return false
-    }
+    override fun collapseShade() {}
     override fun collapseShade(animate: Boolean) {}
     override fun collapseOnMainThread() {}
+    override fun collapseShadeForActivityStart() {}
     override fun makeExpandedInvisible() {}
     override fun makeExpandedVisible(force: Boolean) {}
     override fun isExpandedVisible(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index fdc7eec..e8d9c35 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -33,7 +33,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -44,14 +43,13 @@
 
 import dagger.Lazy;
 
-import java.util.ArrayList;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
 /** An implementation of {@link ShadeController}. */
 @SysUISingleton
-public final class ShadeControllerImpl implements ShadeController {
+public final class ShadeControllerImpl extends BaseShadeControllerImpl {
 
     private static final String TAG = "ShadeControllerImpl";
     private static final boolean SPEW = false;
@@ -60,7 +58,6 @@
 
     private final CommandQueue mCommandQueue;
     private final Executor mMainExecutor;
-    private final LogBuffer mTouchLog;
     private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -73,12 +70,9 @@
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final Lazy<NotificationGutsManager> mGutsManager;
 
-    private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
-
     private boolean mExpandedVisible;
     private boolean mLockscreenOrShadeVisible;
 
-    private NotificationPresenter mPresenter;
     private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     private ShadeVisibilityListener mShadeVisibilityListener;
 
@@ -99,9 +93,13 @@
             Lazy<AssistManager> assistManagerLazy,
             Lazy<NotificationGutsManager> gutsManager
     ) {
+        super(touchLog,
+                commandQueue,
+                statusBarKeyguardViewManager,
+                notificationShadeWindowController,
+                assistManagerLazy);
         mCommandQueue = commandQueue;
         mMainExecutor = mainExecutor;
-        mTouchLog = touchLog;
         mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
         mShadeViewControllerLazy = shadeViewControllerLazy;
         mStatusBarStateController = statusBarStateController;
@@ -117,7 +115,7 @@
 
     @Override
     public boolean isShadeEnabled() {
-        return true;
+        return mCommandQueue.panelsEnabled() && mDeviceProvisionedController.isCurrentUserSetup();
     }
 
     @Override
@@ -125,20 +123,16 @@
         // Make our window larger and the panel expanded.
         makeExpandedVisible(true /* force */);
         getShadeViewController().expand(false /* animate */);
-        mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
+        getCommandQueue().recomputeDisableFlags(mDisplayId, false /* animate */);
     }
 
     @Override
     public void animateCollapseShade(int flags, boolean force, boolean delayed,
             float speedUpFactor) {
         if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) {
-            runPostCollapseRunnables();
+            runPostCollapseActions();
             return;
         }
-        if (SPEW) {
-            Log.d(TAG,
-                    "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
-        }
         if (getNotificationShadeWindowView() != null
                 && getShadeViewController().canBeCollapsed()
                 && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
@@ -151,28 +145,19 @@
     }
 
     @Override
-    public void animateExpandShade() {
-        if (!mCommandQueue.panelsEnabled()) {
-            return;
-        }
+    protected void expandToNotifications() {
         getShadeViewController().expandToNotifications();
     }
 
     @Override
-    public void animateExpandQs() {
-        if (!mCommandQueue.panelsEnabled()) {
-            return;
-        }
-        // Settings are not available in setup
-        if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
-
+    protected void expandToQs() {
         getShadeViewController().expandToQs();
     }
 
     @Override
     public boolean closeShadeIfOpen() {
         if (!getShadeViewController().isFullyCollapsed()) {
-            mCommandQueue.animateCollapsePanels(
+            getCommandQueue().animateCollapsePanels(
                     CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
             notifyVisibilityChanged(false);
             mAssistManagerLazy.get().hideAssist();
@@ -181,11 +166,6 @@
     }
 
     @Override
-    public boolean isKeyguard() {
-        return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
-    }
-
-    @Override
     public boolean isShadeFullyOpen() {
         return getShadeViewController().isShadeFullyExpanded();
     }
@@ -224,46 +204,34 @@
     }
 
     @Override
-    public void addPostCollapseAction(Runnable action) {
-        mPostCollapseRunnables.add(action);
+    public void collapseShade() {
+        collapseShadeInternal();
     }
 
-    @Override
-    public void runPostCollapseRunnables() {
-        ArrayList<Runnable> clonedList = new ArrayList<>(mPostCollapseRunnables);
-        mPostCollapseRunnables.clear();
-        int size = clonedList.size();
-        for (int i = 0; i < size; i++) {
-            clonedList.get(i).run();
-        }
-        mStatusBarKeyguardViewManager.readyForKeyguardDone();
-    }
-
-    @Override
-    public boolean collapseShade() {
+    private boolean collapseShadeInternal() {
         if (!getShadeViewController().isFullyCollapsed()) {
             // close the shade if it was open
             animateCollapseShadeForcedDelayed();
             notifyVisibilityChanged(false);
-
             return true;
         } else {
             return false;
         }
     }
 
+
     @Override
     public void collapseShade(boolean animate) {
         if (animate) {
-            boolean willCollapse = collapseShade();
+            boolean willCollapse = collapseShadeInternal();
             if (!willCollapse) {
-                runPostCollapseRunnables();
+                runPostCollapseActions();
             }
-        } else if (!mPresenter.isPresenterFullyCollapsed()) {
+        } else if (!getNotifPresenter().isPresenterFullyCollapsed()) {
             instantCollapseShade();
             notifyVisibilityChanged(false);
         } else {
-            runPostCollapseRunnables();
+            runPostCollapseActions();
         }
     }
 
@@ -296,46 +264,16 @@
         }
     }
 
-    private void onClosingFinished() {
-        runPostCollapseRunnables();
-        if (!mPresenter.isPresenterFullyCollapsed()) {
-            // if we set it not to be focusable when collapsing, we have to undo it when we aborted
-            // the closing
-            mNotificationShadeWindowController.setNotificationShadeFocusable(true);
-        }
-    }
-
-    @Override
-    public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
-        if (mPresenter.isPresenterFullyCollapsed()
-                && !mPresenter.isCollapsing()
-                && isLaunchForActivity) {
-            onClosingFinished();
-        } else {
-            collapseShade(true /* animate */);
-        }
-    }
-
-    @Override
-    public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
-        if (!mPresenter.isCollapsing()) {
-            onClosingFinished();
-        }
-        if (launchIsFullScreen) {
-            instantCollapseShade();
-        }
-    }
-
     @Override
     public void instantCollapseShade() {
         getShadeViewController().instantCollapse();
-        runPostCollapseRunnables();
+        runPostCollapseActions();
     }
 
     @Override
     public void makeExpandedVisible(boolean force) {
         if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
-        if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
+        if (!force && (mExpandedVisible || !getCommandQueue().panelsEnabled())) {
             return;
         }
 
@@ -346,7 +284,7 @@
         mNotificationShadeWindowController.setPanelVisible(true);
 
         notifyVisibilityChanged(true);
-        mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
+        getCommandQueue().recomputeDisableFlags(mDisplayId, !force /* animate */);
         notifyExpandedVisibleChanged(true);
     }
 
@@ -377,9 +315,9 @@
                 -1 /* y */,
                 true /* resetMenu */);
 
-        runPostCollapseRunnables();
+        runPostCollapseActions();
         notifyExpandedVisibleChanged(false);
-        mCommandQueue.recomputeDisableFlags(
+        getCommandQueue().recomputeDisableFlags(
                 mDisplayId,
                 getShadeViewController().shouldHideStatusBarIconsWhenExpanded());
 
@@ -421,11 +359,6 @@
     }
 
     @Override
-    public void setNotificationPresenter(NotificationPresenter presenter) {
-        mPresenter = presenter;
-    }
-
-    @Override
     public void setNotificationShadeWindowViewController(
             NotificationShadeWindowViewController controller) {
         mNotificationShadeWindowViewController = controller;
@@ -441,8 +374,8 @@
 
     @Override
     public void start() {
-        TouchLogger.logTouchesTo(mTouchLog);
-        getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables);
+        super.start();
+        getShadeViewController().setTrackingStartedListener(this::runPostCollapseActions);
         getShadeViewController().setOpenCloseListener(
                 new OpenCloseListener() {
                     @Override
@@ -456,4 +389,16 @@
                     }
                 });
     }
+
+    @Override
+    public void collapseShadeForActivityStart() {
+        if (isExpandedVisible() && !mStatusBarKeyguardViewManager.isBouncerShowing()) {
+            animateCollapseShadeForcedDelayed();
+        } else {
+            // Do it after DismissAction has been processed to conserve the
+            // needed ordering.
+            mMainExecutor.execute(this::runPostCollapseActions);
+        }
+    }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
new file mode 100644
index 0000000..10b9db0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.dagger.ShadeTouchLog
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shade.ShadeController.ShadeVisibilityListener
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+/**
+ * Implementation of ShadeController backed by scenes instead of NPVC.
+ *
+ * TODO(b/300258424) rename to ShadeControllerImpl and inline/delete all the deprecated methods
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class ShadeControllerSceneImpl
+@Inject
+constructor(
+    @Background private val scope: CoroutineScope,
+    private val shadeInteractor: ShadeInteractor,
+    private val sceneInteractor: SceneInteractor,
+    private val deviceEntryInteractor: DeviceEntryInteractor,
+    private val notificationStackScrollLayout: NotificationStackScrollLayout,
+    @ShadeTouchLog private val touchLog: LogBuffer,
+    commandQueue: CommandQueue,
+    statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+    notificationShadeWindowController: NotificationShadeWindowController,
+    assistManagerLazy: Lazy<AssistManager>,
+) :
+    BaseShadeControllerImpl(
+        touchLog,
+        commandQueue,
+        statusBarKeyguardViewManager,
+        notificationShadeWindowController,
+        assistManagerLazy,
+    ) {
+
+    init {
+        scope.launch {
+            shadeInteractor.isAnyExpanded.collect {
+                if (!it) {
+                    runPostCollapseActions()
+                }
+            }
+        }
+    }
+
+    override fun isShadeEnabled() = shadeInteractor.isShadeEnabled.value
+
+    override fun isShadeFullyOpen(): Boolean = shadeInteractor.isAnyFullyExpanded.value
+
+    override fun isExpandingOrCollapsing(): Boolean =
+        shadeInteractor.anyExpansion.value > 0f && shadeInteractor.anyExpansion.value < 1f
+
+    override fun instantExpandShade() {
+        // Do nothing
+    }
+
+    override fun instantCollapseShade() {
+        // TODO(b/315921512) add support for instant transition
+        sceneInteractor.changeScene(
+            SceneModel(getCollapseDestinationScene(), "instant"),
+            "hide shade"
+        )
+    }
+
+    override fun animateCollapseShade(
+        flags: Int,
+        force: Boolean,
+        delayed: Boolean,
+        speedUpFactor: Float
+    ) {
+        if (!force && !shadeInteractor.isAnyExpanded.value) {
+            runPostCollapseActions()
+            return
+        }
+        if (
+            shadeInteractor.isAnyExpanded.value &&
+                flags and CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL == 0
+        ) {
+            // release focus immediately to kick off focus change transition
+            notificationShadeWindowController.setNotificationShadeFocusable(false)
+            notificationStackScrollLayout.cancelExpandHelper()
+            sceneInteractor.changeScene(
+                SceneModel(SceneKey.Shade, null),
+                "ShadeController.animateExpandShade"
+            )
+            if (delayed) {
+                scope.launch {
+                    delay(125)
+                    animateCollapseShadeInternal()
+                }
+            } else {
+                animateCollapseShadeInternal()
+            }
+        }
+    }
+
+    private fun animateCollapseShadeInternal() {
+        sceneInteractor.changeScene(
+            SceneModel(getCollapseDestinationScene(), "ShadeController.animateCollapseShade"),
+            "ShadeController.animateCollapseShade"
+        )
+    }
+
+    private fun getCollapseDestinationScene(): SceneKey {
+        return if (deviceEntryInteractor.isDeviceEntered.value) {
+            SceneKey.Gone
+        } else {
+            SceneKey.Lockscreen
+        }
+    }
+
+    override fun cancelExpansionAndCollapseShade() {
+        // TODO do we need to actually cancel the touch session?
+        animateCollapseShade()
+    }
+
+    override fun closeShadeIfOpen(): Boolean {
+        if (shadeInteractor.isAnyExpanded.value) {
+            commandQueue.animateCollapsePanels(
+                CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+                true /* force */
+            )
+            assistManagerLazy.get().hideAssist()
+        }
+        return false
+    }
+
+    override fun collapseShade() {
+        animateCollapseShadeForcedDelayed()
+    }
+
+    override fun collapseShade(animate: Boolean) {
+        if (animate) {
+            animateCollapseShade()
+        } else {
+            instantCollapseShade()
+        }
+    }
+
+    override fun collapseOnMainThread() {
+        // TODO if this works with delegation alone, we can deprecate and delete
+        collapseShade()
+    }
+
+    override fun expandToNotifications() {
+        sceneInteractor.changeScene(
+            SceneModel(SceneKey.Shade, null),
+            "ShadeController.animateExpandShade"
+        )
+    }
+
+    override fun expandToQs() {
+        sceneInteractor.changeScene(
+            SceneModel(SceneKey.QuickSettings, null),
+            "ShadeController.animateExpandQs"
+        )
+    }
+
+    override fun setVisibilityListener(listener: ShadeVisibilityListener) {
+        scope.launch { sceneInteractor.isVisible.collect { listener.expandedVisibleChanged(it) } }
+    }
+
+    @ExperimentalCoroutinesApi
+    override fun collapseShadeForActivityStart() {
+        if (shadeInteractor.isAnyExpanded.value) {
+            animateCollapseShadeForcedDelayed()
+        } else {
+            runPostCollapseActions()
+        }
+    }
+
+    override fun postAnimateCollapseShade() {
+        animateCollapseShade()
+    }
+
+    override fun postAnimateForceCollapseShade() {
+        animateCollapseShadeForced()
+    }
+
+    override fun postAnimateExpandQs() {
+        expandToQs()
+    }
+
+    override fun postOnShadeExpanded(action: Runnable) {
+        // TODO verify that clicking "reply" in a work profile notification launches the app
+        // TODO verify that there's not a way to replace and deprecate this method
+        scope.launch {
+            shadeInteractor.isAnyFullyExpanded.first { it }
+            action.run()
+        }
+    }
+
+    override fun makeExpandedInvisible() {
+        // Do nothing
+    }
+
+    override fun makeExpandedVisible(force: Boolean) {
+        // Do nothing
+    }
+
+    override fun isExpandedVisible(): Boolean {
+        return sceneInteractor.desiredScene.value.key != SceneKey.Gone
+    }
+
+    override fun onStatusBarTouch(event: MotionEvent) {
+        // The only call to this doesn't happen with KeyguardShadeMigrationNssl enabled
+        throw UnsupportedOperationException()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index c057147..fc2c3ee 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -53,6 +53,20 @@
 
         @Provides
         @SysUISingleton
+        fun provideShadeController(
+            sceneContainerFlags: SceneContainerFlags,
+            sceneContainerOn: Provider<ShadeControllerSceneImpl>,
+            sceneContainerOff: Provider<ShadeControllerImpl>
+        ): ShadeController {
+            return if (sceneContainerFlags.isEnabled()) {
+                sceneContainerOn.get()
+            } else {
+                sceneContainerOff.get()
+            }
+        }
+
+        @Provides
+        @SysUISingleton
         fun provideShadeAnimationInteractor(
             sceneContainerFlags: SceneContainerFlags,
             sceneContainerOn: Provider<ShadeAnimationInteractorSceneContainerImpl>,
@@ -79,8 +93,4 @@
     abstract fun bindsShadeViewController(
         notificationPanelViewController: NotificationPanelViewController
     ): ShadeViewController
-
-    @Binds
-    @SysUISingleton
-    abstract fun bindsShadeController(shadeControllerImpl: ShadeControllerImpl): ShadeController
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 31a4de4..43ede2a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -29,7 +29,7 @@
     val isShadeEnabled: StateFlow<Boolean>
 
     /** Whether either the shade or QS is fully expanded. */
-    val isAnyFullyExpanded: Flow<Boolean>
+    val isAnyFullyExpanded: StateFlow<Boolean>
 
     /** Whether the Shade is fully expanded. */
     val isShadeFullyExpanded: Flow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 6defbcf..55dd674 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -34,7 +34,7 @@
     override val isQsBypassingShade: Flow<Boolean> = inactiveFlowBoolean
     override val isQsFullscreen: Flow<Boolean> = inactiveFlowBoolean
     override val anyExpansion: StateFlow<Float> = inactiveFlowFloat
-    override val isAnyFullyExpanded: Flow<Boolean> = inactiveFlowBoolean
+    override val isAnyFullyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
     override val isShadeFullyExpanded: Flow<Boolean> = inactiveFlowBoolean
     override val isAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
     override val isUserInteractingWithShade: Flow<Boolean> = inactiveFlowBoolean
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 6407b5a..a71cf95 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -55,12 +55,20 @@
     private val baseShadeInteractor: BaseShadeInteractor,
 ) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
     override val isShadeEnabled: StateFlow<Boolean> =
-        disableFlagsRepository.disableFlags
-            .map { it.isShadeEnabled() }
+        combine(
+                deviceProvisioningRepository.isFactoryResetProtectionActive,
+                disableFlagsRepository.disableFlags,
+            ) { isFrpActive, isDisabledByFlags ->
+                isDisabledByFlags.isShadeEnabled() && !isFrpActive
+            }
+            .distinctUntilChanged()
             .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
 
-    override val isAnyFullyExpanded: Flow<Boolean> =
-        anyExpansion.map { it >= 1f }.distinctUntilChanged()
+    override val isAnyFullyExpanded: StateFlow<Boolean> =
+        anyExpansion
+            .map { it >= 1f }
+            .distinctUntilChanged()
+            .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
 
     override val isShadeFullyExpanded: Flow<Boolean> =
         baseShadeInteractor.shadeExpansion.map { it >= 1f }.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index fc84973..28d4457 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -35,7 +35,6 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
@@ -61,7 +60,6 @@
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -73,6 +71,7 @@
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -85,6 +84,8 @@
 
 import dalvik.annotation.optimization.NeverCompile;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -99,8 +100,6 @@
 
 import javax.inject.Inject;
 
-import kotlin.Unit;
-
 /** Platform implementation of the network controller. **/
 @SysUISingleton
 public class NetworkControllerImpl extends BroadcastReceiver
@@ -350,7 +349,7 @@
         // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
         updateAirplaneMode(true /* force callback */);
         mUserTracker = userTracker;
-        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
+        mUserTracker.addCallback(mUserChangedCallback, mBgExecutor);
 
         deviceProvisionedController.addCallback(new DeviceProvisionedListener() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index 2d5afd5..3cdb2cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -21,8 +21,6 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -53,14 +51,11 @@
      */
     private final Set<NotificationEntry> mExpandedGroups = new HashSet<>();
 
-    private final FeatureFlags mFeatureFlags;
-
     @Inject
     public GroupExpansionManagerImpl(DumpManager dumpManager,
-            GroupMembershipManager groupMembershipManager, FeatureFlags featureFlags) {
+            GroupMembershipManager groupMembershipManager) {
         mDumpManager = dumpManager;
         mGroupMembershipManager = groupMembershipManager;
-        mFeatureFlags = featureFlags;
     }
 
     /**
@@ -86,10 +81,8 @@
     };
 
     public void attach(NotifPipeline pipeline) {
-        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
-            mDumpManager.registerDumpable(this);
-            pipeline.addOnBeforeRenderListListener(mNotifTracker);
-        }
+        mDumpManager.registerDumpable(this);
+        pipeline.addOnBeforeRenderListListener(mNotifTracker);
     }
 
     @Override
@@ -105,8 +98,7 @@
     @Override
     public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
         NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry);
-        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)
-                && entry.getParent() == null) {
+        if (entry.getParent() == null) {
             if (expanded) {
                 throw new IllegalArgumentException("Cannot expand group that is not attached");
             } else {
@@ -124,7 +116,7 @@
         }
 
         // Only notify listeners if something changed.
-        if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE) || changed) {
+        if (changed) {
             sendOnGroupExpandedChange(entry, expanded);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
index cb79353..da12479 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
@@ -22,8 +22,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -38,25 +36,17 @@
  */
 @SysUISingleton
 public class GroupMembershipManagerImpl implements GroupMembershipManager {
-    FeatureFlagsClassic mFeatureFlags;
-
     @Inject
-    public GroupMembershipManagerImpl(FeatureFlagsClassic featureFlags) {
-        mFeatureFlags = featureFlags;
-    }
+    public GroupMembershipManagerImpl() {}
 
     @Override
     public boolean isGroupSummary(@NonNull NotificationEntry entry) {
-        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
-            if (entry.getParent() == null) {
-                // The entry is not attached, so it doesn't count.
-                return false;
-            }
-            // If entry is a summary, its parent is a GroupEntry with summary = entry.
-            return entry.getParent().getSummary() == entry;
-        } else {
-            return getGroupSummary(entry) == entry;
+        if (entry.getParent() == null) {
+            // The entry is not attached, so it doesn't count.
+            return false;
         }
+        // If entry is a summary, its parent is a GroupEntry with summary = entry.
+        return entry.getParent().getSummary() == entry;
     }
 
     @Nullable
@@ -70,12 +60,8 @@
 
     @Override
     public boolean isChildInGroup(@NonNull NotificationEntry entry) {
-        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
-            // An entry is a child if it's not a summary or top level entry, but it is attached.
-            return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null;
-        } else {
-            return !isTopLevelEntry(entry);
-        }
+        // An entry is a child if it's not a summary or top level entry, but it is attached.
+        return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index aca8b64..342828c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -6,6 +6,7 @@
 import android.net.Uri
 import android.os.Handler
 import android.os.HandlerExecutor
+import android.os.HandlerThread
 import android.os.UserHandle
 import android.provider.Settings
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -87,6 +88,7 @@
             secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)
     private val onStateChangedListeners = ListenerSet<Consumer<String>>()
     private var hideSilentNotificationsOnLockscreen: Boolean = false
+    private val handlerThread: HandlerThread = HandlerThread("KeyguardNotificationVis")
 
     private val userTrackerCallback = object : UserTracker.Callback {
         override fun onUserChanged(newUser: Int, userContext: Context) {
@@ -154,7 +156,9 @@
                 notifyStateChanged("onStatusBarUpcomingStateChanged")
             }
         })
-        userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler))
+        handlerThread.start()
+        userTracker.addCallback(userTrackerCallback,
+                   HandlerExecutor(handlerThread.getThreadHandler()))
     }
 
     override fun addOnStateChangedListener(listener: Consumer<String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 4349b3b..c6832bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -247,6 +247,9 @@
 
     public void setUpWithContainer(NotificationListContainer listContainer) {
         mListContainer = listContainer;
+        if (mLogging) {
+            mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
+        }
     }
 
     @Override
@@ -294,7 +297,9 @@
                 lockscreen = mLockscreen != null && mLockscreen;
             }
             mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications());
-            mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
+            if (mListContainer != null) {
+                mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
+            }
             // Sometimes, the transition from lockscreenOrShadeVisible=false ->
             // lockscreenOrShadeVisible=true doesn't cause the scroller to emit child location
             // events. Hence generate one ourselves to guarantee that we're reporting visible
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
index d10b556..8bc8e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
@@ -22,11 +22,9 @@
 import android.view.LayoutInflater
 import android.view.View
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
-import com.android.systemui.statusbar.notification.row.NotificationRowModule.NOTIF_REMOTEVIEWS_FACTORIES
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import javax.inject.Named
 
 /**
  * Implementation of [NotifLayoutInflaterFactory]. This class uses a set of
@@ -37,8 +35,7 @@
 constructor(
     @Assisted private val row: ExpandableNotificationRow,
     @Assisted @InflationFlag val layoutType: Int,
-    @Named(NOTIF_REMOTEVIEWS_FACTORIES)
-    private val remoteViewsFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory>
+    private val notifRemoteViewsFactoryContainer: NotifRemoteViewsFactoryContainer
 ) : LayoutInflater.Factory2 {
 
     override fun onCreateView(
@@ -49,7 +46,7 @@
     ): View? {
         var handledFactory: NotifRemoteViewsFactory? = null
         var result: View? = null
-        for (layoutFactory in remoteViewsFactories) {
+        for (layoutFactory in notifRemoteViewsFactoryContainer.factories) {
             layoutFactory.instantiate(row, layoutType, parent, name, context, attrs)?.run {
                 check(handledFactory == null) {
                     "$layoutFactory tries to produce name:$name with type:$layoutType. " +
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
new file mode 100644
index 0000000..99177c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.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.statusbar.notification.row
+
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+interface NotifRemoteViewsFactoryContainer {
+    val factories: Set<NotifRemoteViewsFactory>
+}
+
+class NotifRemoteViewsFactoryContainerImpl
+@Inject
+constructor(
+    featureFlags: FeatureFlags,
+    precomputedTextViewFactory: PrecomputedTextViewFactory,
+    bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
+    callLayoutSetDataAsyncFactory: CallLayoutSetDataAsyncFactory,
+) : NotifRemoteViewsFactoryContainer {
+    override val factories: Set<NotifRemoteViewsFactory> = buildSet {
+        add(precomputedTextViewFactory)
+        if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
+            add(bigPictureLayoutInflaterFactory)
+        }
+        if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
+            add(callLayoutSetDataAsyncFactory)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index 46ddba4..200a08a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -17,26 +17,15 @@
 package com.android.systemui.statusbar.notification.row;
 
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 
 import dagger.Binds;
 import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.ElementsIntoSet;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.inject.Named;
 
 /**
  * Dagger Module containing notification row and view inflation implementations.
  */
 @Module
 public abstract class NotificationRowModule {
-    public static final String NOTIF_REMOTEVIEWS_FACTORIES =
-            "notif_remoteviews_factories";
 
     /**
      * Provides notification row content binder instance.
@@ -54,24 +43,11 @@
     public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
             NotifRemoteViewCacheImpl cacheImpl);
 
-    /** Provides view factories to be inflated in notification content. */
-    @Provides
-    @ElementsIntoSet
-    @Named(NOTIF_REMOTEVIEWS_FACTORIES)
-    static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
-            FeatureFlags featureFlags,
-            PrecomputedTextViewFactory precomputedTextViewFactory,
-            BigPictureLayoutInflaterFactory bigPictureLayoutInflaterFactory,
-            CallLayoutSetDataAsyncFactory callLayoutSetDataAsyncFactory
-    ) {
-        final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
-        replacementFactories.add(precomputedTextViewFactory);
-        if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
-            replacementFactories.add(bigPictureLayoutInflaterFactory);
-        }
-        if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
-            replacementFactories.add(callLayoutSetDataAsyncFactory);
-        }
-        return replacementFactories;
-    }
+    /**
+     * Provides notification remote view factory container
+     */
+    @Binds
+    @SysUISingleton
+    public abstract NotifRemoteViewsFactoryContainer provideNotifRemoteViewsFactoryContainer(
+            NotifRemoteViewsFactoryContainerImpl containerImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 63194c3..8a56da3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -797,18 +797,7 @@
                             }
                         }
                         if (dismissShade) {
-                            if (
-                                shadeControllerLazy.get().isExpandedVisible &&
-                                    !statusBarKeyguardViewManagerLazy.get().isBouncerShowing
-                            ) {
-                                shadeControllerLazy.get().animateCollapseShadeForcedDelayed()
-                            } else {
-                                // Do it after DismissAction has been processed to conserve the
-                                // needed ordering.
-                                postOnUiThread {
-                                    shadeControllerLazy.get().runPostCollapseRunnables()
-                                }
-                            }
+                            shadeControllerLazy.get().collapseShadeForActivityStart()
                         }
                         return deferred
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 20d1fff..a2d8d15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -28,6 +28,8 @@
 import android.icu.text.DateTimePatternGenerator;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -48,11 +50,11 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.FontSizeUtils;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -106,6 +108,7 @@
     private final int mAmPmStyle;
     private boolean mShowSeconds;
     private Handler mSecondsHandler;
+    private HandlerThread mHandlerThread;
 
     // Fields to cache the width so the clock remains at an approximately constant width
     private int mCharsAtCurrentWidth = -1;
@@ -146,6 +149,8 @@
         }
         mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
         mUserTracker = Dependency.get(UserTracker.class);
+        mHandlerThread = new HandlerThread("Clock");
+        mHandlerThread.start();
 
         setIncludeFontPadding(false);
     }
@@ -205,7 +210,8 @@
             Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
                     StatusBarIconController.ICON_HIDE_LIST);
             mCommandQueue.addCallback(this);
-            mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+            mUserTracker.addCallback(mUserChangedCallback,
+                       new HandlerExecutor(mHandlerThread.getThreadHandler()));
             mCurrentUserId = mUserTracker.getUserId();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index b7d8ee3..a7440d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.UserHandle;
 
 import androidx.annotation.NonNull;
@@ -51,6 +53,7 @@
     private final UserTracker mUserTracker;
     private AlarmManager mAlarmManager;
     private AlarmManager.AlarmClockInfo mNextAlarm;
+    private HandlerThread mHandlerThread;
 
     private final UserTracker.Callback mUserChangedCallback =
             new UserTracker.Callback() {
@@ -75,7 +78,10 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
         broadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
-        mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
+        mHandlerThread = new HandlerThread("NextAlarmControllerImpl");
+        mHandlerThread.start();
+        mUserTracker.addCallback(mUserChangedCallback,
+                    new HandlerExecutor(mHandlerThread.getThreadHandler()));
         updateNextAlarm();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 9f4a906..6a6efbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -157,7 +157,7 @@
         // TODO: re-register network callback on user change.
         mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
         onUserSwitched(mUserTracker.getUserId());
-        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+        mUserTracker.addCallback(mUserChangedCallback, mBgExecutor);
     }
 
     public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 2ed9d15..0bc0e88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -36,9 +36,9 @@
 
 import com.android.internal.util.UserIcons;
 import com.android.settingslib.drawable.UserIconDrawable;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 
 import java.util.ArrayList;
@@ -66,11 +66,11 @@
     /**
      */
     @Inject
-    public UserInfoControllerImpl(Context context, @Main Executor mainExecutor,
+    public UserInfoControllerImpl(Context context, @Background Executor bgExecutor,
             UserTracker userTracker) {
         mContext = context;
         mUserTracker = userTracker;
-        mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
+        mUserTracker.addCallback(mUserChangedCallback, bgExecutor);
 
         IntentFilter profileFilter = new IntentFilter();
         profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index df210b0..f0b4930 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -29,6 +29,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -81,6 +82,7 @@
     private volatile int mZenMode;
     private long mZenUpdateTime;
     private NotificationManager.Policy mConsolidatedNotificationPolicy;
+    private HandlerThread mHandlerThread;
 
     private final UserTracker.Callback mUserChangedCallback =
             new UserTracker.Callback() {
@@ -133,6 +135,8 @@
                 }
             }
         };
+        mHandlerThread = new HandlerThread("ZenModeControllerImpl");
+        mHandlerThread.start();
         mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
         globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
         updateZenMode(getModeSettingValueFromProvider());
@@ -143,7 +147,8 @@
         mSetupObserver = new SetupObserver(handler);
         mSetupObserver.register();
         mUserManager = context.getSystemService(UserManager.class);
-        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(handler));
+        mUserTracker.addCallback(mUserChangedCallback,
+                new HandlerExecutor(mHandlerThread.getThreadHandler()));
         // This registers the alarm broadcast receiver for the current user
         mUserChangedCallback.onUserChanged(getCurrentUser(), context);
 
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 2b9ad50..77518db 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -480,7 +480,7 @@
             return;
         }
 
-        mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
+        mUserTracker.addCallback(mUserTrackerCallback, mBgExecutor);
 
         mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
 
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 550a65c..f5b4d17 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -26,6 +26,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -38,11 +39,11 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.systemui.DejankUtils;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -98,6 +99,7 @@
     private UserTracker.Callback mCurrentUserTracker;
     private UserTracker mUserTracker;
     private final ComponentName mTunerComponent;
+    private HandlerThread mHandlerThread;
 
     /**
      */
@@ -117,7 +119,8 @@
         mDemoModeController = demoModeController;
         mUserTracker = userTracker;
         mTunerComponent = new ComponentName(mContext, TunerActivity.class);
-
+        mHandlerThread = new HandlerThread("TunerServiceImpl");
+        mHandlerThread.start();
         for (UserInfo user : UserManager.get(mContext).getUsers()) {
             mCurrentUser = user.getUserHandle().getIdentifier();
             if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) {
@@ -135,7 +138,7 @@
             }
         };
         mUserTracker.addCallback(mCurrentUserTracker,
-                new HandlerExecutor(mainHandler));
+                new HandlerExecutor(mHandlerThread.getThreadHandler()));
     }
 
     @Override
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 cf76c0d..74e1339 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
@@ -190,7 +190,7 @@
                         }
                     }
 
-                tracker.addCallback(callback, mainDispatcher.asExecutor())
+                tracker.addCallback(callback, backgroundDispatcher.asExecutor())
                 send(currentSelectionStatus)
 
                 awaitClose { tracker.removeCallback(callback) }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 1e801ae..7c6ad23 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -32,6 +32,8 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.inputmethodservice.InputMethodService;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.util.Log;
 import android.view.Display;
@@ -125,6 +127,7 @@
     private final DisplayTracker mDisplayTracker;
     private final NoteTaskInitializer mNoteTaskInitializer;
     private final Executor mSysUiMainExecutor;
+    private HandlerThread mHandlerThread;
 
     // Listeners and callbacks. Note that we prefer member variable over anonymous class here to
     // avoid the situation that some implementations, like KeyguardUpdateMonitor, use WeakReference
@@ -206,6 +209,8 @@
         mDisplayTracker = displayTracker;
         mNoteTaskInitializer = noteTaskInitializer;
         mSysUiMainExecutor = sysUiMainExecutor;
+        mHandlerThread = new HandlerThread("WMShell");
+        mHandlerThread.start();
     }
 
     @Override
@@ -219,7 +224,8 @@
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
 
         // Subscribe to user changes
-        mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+        mUserTracker.addCallback(mUserChangedCallback,
+                     new HandlerExecutor(mHandlerThread.getThreadHandler()));
 
         mCommandQueue.addCallback(this);
         mPipOptional.ifPresent(this::initPip);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d06457b..be06cc5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -121,7 +121,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
 import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
 import com.android.settingslib.fuelgauge.BatteryStatus;
-import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
@@ -930,7 +929,8 @@
     @Test
     public void trustAgentHasTrust() {
         // WHEN user has trust
-        givenSelectedUserCanSkipBouncerFromTrustedState();
+        mKeyguardUpdateMonitor.onTrustChanged(true, true,
+                mSelectedUserInteractor.getSelectedUserId(), 0, null);
 
         // THEN user is considered as "having trust" and bouncer can be skipped
         Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
@@ -954,7 +954,8 @@
     @Test
     public void trustAgentHasTrust_fingerprintLockout() {
         // GIVEN user has trust
-        givenSelectedUserCanSkipBouncerFromTrustedState();
+        mKeyguardUpdateMonitor.onTrustChanged(true, true,
+                mSelectedUserInteractor.getSelectedUserId(), 0, null);
         Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
                 mSelectedUserInteractor.getSelectedUserId()));
 
@@ -2015,43 +2016,6 @@
     }
 
     @Test
-    public void runFpDetectFlagDisabled_sideFps_keyguardDismissible_fingerprintAuthenticateRuns() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
-
-        // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
-        // will trigger updateBiometricListeningState();
-        clearInvocations(mFingerprintManager);
-        mKeyguardUpdateMonitor.resetBiometricListeningState();
-
-        // GIVEN the user can skip the bouncer
-        givenSelectedUserCanSkipBouncerFromTrustedState();
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
-        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
-        mTestableLooper.processAllMessages();
-
-        // WHEN verify authenticate runs
-        verifyFingerprintAuthenticateCall();
-    }
-
-    @Test
-    public void sideFps_keyguardDismissible_fingerprintDetectRuns() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
-        // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
-        // will trigger updateBiometricListeningState();
-        clearInvocations(mFingerprintManager);
-        mKeyguardUpdateMonitor.resetBiometricListeningState();
-
-        // GIVEN the user can skip the bouncer
-        givenSelectedUserCanSkipBouncerFromTrustedState();
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
-        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
-        mTestableLooper.processAllMessages();
-
-        // WHEN verify detect runs
-        verifyFingerprintDetectCall();
-    }
-
-    @Test
     public void testFingerprintSensorProperties() throws RemoteException {
         mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
                 new ArrayList<>());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index e0c6bba..0ba9abe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -32,12 +32,16 @@
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.QuickSettingsController
 import com.android.systemui.shade.ShadeController
@@ -59,7 +63,6 @@
 import junit.framework.Assert.assertTrue
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import org.junit.Before
 import org.junit.Rule
@@ -76,7 +79,8 @@
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class BackActionInteractorTest : SysuiTestCase() {
-    private val testScope = TestScope()
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
     private val executor = FakeExecutor(FakeSystemClock())
 
     @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@@ -105,6 +109,8 @@
             headsUpManager,
             powerInteractor,
             activeNotificationsInteractor,
+            kosmos.sceneContainerFlags,
+            kosmos::sceneInteractor,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
index e4432f3..0636831 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
@@ -30,13 +30,11 @@
 import android.permission.PermissionGroupUsage
 import android.permission.PermissionManager
 import android.testing.AndroidTestingRunner
-import android.view.View
 import android.widget.LinearLayout
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
-import com.android.systemui.animation.LaunchableView
 import com.android.systemui.appops.AppOpsController
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.privacy.logging.PrivacyLogger
@@ -206,10 +204,7 @@
     @Test
     fun testShowDialogShowsDialogWithView() {
         val parent = LinearLayout(context)
-        val view =
-            object : View(context), LaunchableView {
-                override fun setShouldBlockVisibilityChanges(block: Boolean) {}
-            }
+        val view = OngoingPrivacyChip(context)
         parent.addView(view)
         val usage = createMockPermGroupUsage()
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
index fa02e8c..f98b68f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
@@ -173,7 +173,7 @@
         captor.value.onClick(privacyChip)
         verify(privacyDialogController).showDialog(any(Context::class.java))
         verify(privacyDialogControllerV2, never())
-            .showDialog(any(Context::class.java), any(View::class.java))
+            .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java))
     }
 
     @Test
@@ -186,7 +186,7 @@
         captor.value.onClick(privacyChip)
         verify(privacyDialogController).showDialog(any(Context::class.java))
         verify(privacyDialogControllerV2, never())
-                .showDialog(any(Context::class.java), any(View::class.java))
+                .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java))
     }
 
     @Test
@@ -207,7 +207,7 @@
         captor.value.onClick(privacyChip)
         verify(privacyDialogController, never()).showDialog(any(Context::class.java))
         verify(privacyDialogControllerV2, never())
-            .showDialog(any(Context::class.java), any(View::class.java))
+            .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java))
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index b24b877..c0ef50f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -1069,6 +1069,22 @@
         assertThat(mInternetDialogController.mCallback).isNull();
     }
 
+    @Test
+    public void hasActiveSubId_activeSubIdListIsEmpty_returnFalse() {
+        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{});
+        mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+        assertThat(mInternetDialogController.hasActiveSubId()).isFalse();
+    }
+
+    @Test
+    public void hasActiveSubId_activeSubIdListNotEmpty_returnTrue() {
+        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+        mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+        assertThat(mInternetDialogController.hasActiveSubId()).isTrue();
+    }
+
     private String getResourcesString(String name) {
         return mContext.getResources().getString(getResourcesId(name));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 45f6869..a6e240b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -22,6 +22,7 @@
 import android.testing.ViewUtils
 import android.view.MotionEvent
 import android.view.View
+import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
@@ -61,6 +62,7 @@
     @Mock private lateinit var shadeInteractor: ShadeInteractor
     @Mock private lateinit var powerManager: PowerManager
 
+    private lateinit var parentView: FrameLayout
     private lateinit var containerView: View
     private lateinit var testableLooper: TestableLooper
 
@@ -225,15 +227,38 @@
         assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
     }
 
+    @Test
+    fun onTouchEvent_containerViewDisposed_doesNotIntercept() {
+        // Communal is open.
+        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+
+        initAndAttachContainerView()
+        testableLooper.processAllMessages()
+
+        // Touch events are intercepted.
+        assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+
+        // Container view disposed.
+        underTest.disposeView()
+
+        // Touch events are not intercepted.
+        assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+    }
+
     private fun initAndAttachContainerView() {
         containerView = View(context)
+
+        parentView = FrameLayout(context)
+        parentView.addView(containerView)
+
         // Make view clickable so that dispatchTouchEvent returns true.
         containerView.isClickable = true
 
         underTest.initView(containerView)
         // Attach the view so that flows start collecting.
-        ViewUtils.attachView(containerView)
+        ViewUtils.attachView(parentView)
         // Give the view a size so that determining if a touch starts at the right edge works.
+        parentView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
         containerView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index a11839c..6681cee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -74,10 +74,12 @@
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
@@ -480,6 +482,7 @@
         }
 
     @Test
+    @Ignore("b/321332798")
     fun setsUpCommunalHubLayout_whenFlagEnabled() {
         if (!isComposeAvailable()) {
             return
@@ -511,6 +514,8 @@
         }
 
         whenever(mGlanceableHubContainerController.isEnabled()).thenReturn(false)
+        whenever(mGlanceableHubContainerController.enabledState())
+            .thenReturn(MutableStateFlow(false))
 
         val mockCommunalPlaceholder = mock(View::class.java)
         val fakeViewIndex = 20
@@ -520,8 +525,7 @@
 
         underTest.setupCommunalHubLayout()
 
-        // No adding or removing of views occurs.
-        verify(view, times(0)).removeView(mockCommunalPlaceholder)
+        // No adding of views occurs.
         verify(view, times(0)).addView(any(), eq(fakeViewIndex))
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index c4911a4..cc79ca4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -22,15 +22,18 @@
 import android.view.WindowManager
 import androidx.test.filters.SmallTest
 import com.android.internal.statusbar.IStatusBarService
-import com.android.keyguard.TestScopeProvider
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.assist.AssistManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
@@ -64,6 +67,8 @@
     private val executor = FakeExecutor(FakeSystemClock())
     private val testDispatcher = StandardTestDispatcher()
     private val activeNotificationsRepository = ActiveNotificationListRepository()
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
 
     @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var keyguardStateController: KeyguardStateController
@@ -84,12 +89,14 @@
 
     private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
         WindowRootViewVisibilityInteractor(
-            TestScopeProvider.getTestScope(),
+            testScope,
             WindowRootViewVisibilityRepository(iStatusBarService, executor),
             FakeKeyguardRepository(),
             headsUpManager,
             PowerInteractorFactory.create().powerInteractor,
-            ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher)
+            ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher),
+            kosmos.sceneContainerFlags,
+            kosmos::sceneInteractor,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index 65697b73..36f643a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -24,7 +24,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -53,8 +52,8 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -78,28 +77,22 @@
 
     private lateinit var coordinator: ConversationCoordinator
 
-    private val featureFlags = FakeFeatureFlagsClassic()
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        coordinator = ConversationCoordinator(
-            peopleNotificationIdentifier,
-            conversationIconManager,
-            HighPriorityProvider(
+        coordinator =
+            ConversationCoordinator(
                 peopleNotificationIdentifier,
-                GroupMembershipManagerImpl(featureFlags)
-            ),
-            headerController
-        )
+                conversationIconManager,
+                HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()),
+                headerController
+            )
         whenever(channel.isImportantConversation).thenReturn(true)
 
         coordinator.attach(pipeline)
 
         // capture arguments:
-        promoter = withArgCaptor {
-            verify(pipeline).addPromoter(capture())
-        }
+        promoter = withArgCaptor { verify(pipeline).addPromoter(capture()) }
         beforeRenderListListener = withArgCaptor {
             verify(pipeline).addOnBeforeRenderListListener(capture())
         }
@@ -111,10 +104,10 @@
         entry = NotificationEntryBuilder().setChannel(channel).build()
 
         val section = NotifSection(peopleAlertingSectioner, 0)
-        entryA = NotificationEntryBuilder().setChannel(channel)
-            .setSection(section).setTag("A").build()
-        entryB = NotificationEntryBuilder().setChannel(channel)
-            .setSection(section).setTag("B").build()
+        entryA =
+            NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("A").build()
+        entryB =
+            NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("B").build()
     }
 
     @Test
@@ -129,11 +122,12 @@
         val altChildA = NotificationEntryBuilder().setTag("A").build()
         val altChildB = NotificationEntryBuilder().setTag("B").build()
         val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build()
-        val groupEntry = GroupEntryBuilder()
-            .setParent(GroupEntry.ROOT_ENTRY)
-            .setSummary(summary)
-            .setChildren(listOf(entry, altChildA, altChildB))
-            .build()
+        val groupEntry =
+            GroupEntryBuilder()
+                .setParent(GroupEntry.ROOT_ENTRY)
+                .setSummary(summary)
+                .setChildren(listOf(entry, altChildA, altChildB))
+                .build()
         assertTrue(promoter.shouldPromoteToTopLevel(entry))
         assertFalse(promoter.shouldPromoteToTopLevel(altChildA))
         assertFalse(promoter.shouldPromoteToTopLevel(altChildB))
@@ -146,41 +140,42 @@
     @Test
     fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() {
         // GIVEN
-        val alertingEntry = NotificationEntryBuilder().setChannel(channel)
-                .setImportance(IMPORTANCE_DEFAULT).build()
+        val alertingEntry =
+            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_DEFAULT).build()
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry))
-                .thenReturn(TYPE_PERSON)
+            .thenReturn(TYPE_PERSON)
 
         // put alerting people notifications in this section
         assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue()
-       }
+    }
 
     @Test
     fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() {
         // GIVEN
-        val silentEntry = NotificationEntryBuilder().setChannel(channel)
-                .setImportance(IMPORTANCE_LOW).build()
+        val silentEntry =
+            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry))
-                .thenReturn(TYPE_PERSON)
+            .thenReturn(TYPE_PERSON)
 
         // THEN put silent people notifications in this section
         assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue()
         // People Alerting sectioning happens before the silent one.
-        // It claims high important conversations and rest of conversations will be considered as silent.
+        // It claims high important conversations and rest of conversations will be considered as
+        // silent.
         assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isFalse()
     }
 
     @Test
     fun testNotInPeopleSection() {
         // GIVEN
-        val entry = NotificationEntryBuilder().setChannel(channel)
-                .setImportance(IMPORTANCE_LOW).build()
-        val importantEntry = NotificationEntryBuilder().setChannel(channel)
-                .setImportance(IMPORTANCE_HIGH).build()
+        val entry =
+            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
+        val importantEntry =
+            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_HIGH).build()
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
-                .thenReturn(TYPE_NON_PERSON)
+            .thenReturn(TYPE_NON_PERSON)
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry))
-                .thenReturn(TYPE_NON_PERSON)
+            .thenReturn(TYPE_NON_PERSON)
 
         // THEN - only put people notification either silent or alerting
         assertThat(peopleSilentSectioner.isInSection(entry)).isFalse()
@@ -190,19 +185,23 @@
     @Test
     fun testInAlertingPeopleSectionWhenThereIsAnImportantChild() {
         // GIVEN
-        val altChildA = NotificationEntryBuilder().setTag("A")
-                .setImportance(IMPORTANCE_DEFAULT).build()
-        val altChildB = NotificationEntryBuilder().setTag("B")
-                .setImportance(IMPORTANCE_LOW).build()
-        val summary = NotificationEntryBuilder().setId(2)
-                .setImportance(IMPORTANCE_LOW).setChannel(channel).build()
-        val groupEntry = GroupEntryBuilder()
+        val altChildA =
+            NotificationEntryBuilder().setTag("A").setImportance(IMPORTANCE_DEFAULT).build()
+        val altChildB = NotificationEntryBuilder().setTag("B").setImportance(IMPORTANCE_LOW).build()
+        val summary =
+            NotificationEntryBuilder()
+                .setId(2)
+                .setImportance(IMPORTANCE_LOW)
+                .setChannel(channel)
+                .build()
+        val groupEntry =
+            GroupEntryBuilder()
                 .setParent(GroupEntry.ROOT_ENTRY)
                 .setSummary(summary)
                 .setChildren(listOf(altChildA, altChildB))
                 .build()
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary))
-                .thenReturn(TYPE_PERSON)
+            .thenReturn(TYPE_PERSON)
         // THEN
         assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
deleted file mode 100644
index c1ffa64..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.render
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-
-@SmallTest
-class GroupMembershipManagerTest : SysuiTestCase() {
-    private lateinit var gmm: GroupMembershipManagerImpl
-
-    private val featureFlags = FakeFeatureFlagsClassic()
-
-    @Before
-    fun setUp() {
-        gmm = GroupMembershipManagerImpl(featureFlags)
-    }
-
-    @Test
-    fun testIsChildInGroup_topLevel() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-        val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
-        assertThat(gmm.isChildInGroup(topLevelEntry)).isFalse()
-    }
-
-    @Test
-    fun testIsChildInGroup_noParent_old() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-        val noParentEntry = NotificationEntryBuilder().setParent(null).build()
-        assertThat(gmm.isChildInGroup(noParentEntry)).isTrue()
-    }
-
-    @Test
-    fun testIsChildInGroup_noParent_new() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-        val noParentEntry = NotificationEntryBuilder().setParent(null).build()
-        assertThat(gmm.isChildInGroup(noParentEntry)).isFalse()
-    }
-    @Test
-    fun testIsChildInGroup_summary_old() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
-        assertThat(gmm.isChildInGroup(summary)).isTrue()
-    }
-
-    @Test
-    fun testIsChildInGroup_summary_new() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
-        assertThat(gmm.isChildInGroup(summary)).isFalse()
-    }
-
-    @Test
-    fun testIsChildInGroup_child() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-        val childEntry = NotificationEntryBuilder().build()
-        assertThat(gmm.isChildInGroup(childEntry)).isTrue()
-    }
-
-    @Test
-    fun testIsGroupSummary_topLevelEntry() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-        val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
-        assertThat(gmm.isGroupSummary(entry)).isFalse()
-    }
-
-    @Test
-    fun testIsGroupSummary_summary() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
-        assertThat(gmm.isGroupSummary(summary)).isTrue()
-    }
-
-    @Test
-    fun testIsGroupSummary_child() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
-
-        assertThat(gmm.isGroupSummary(entry)).isFalse()
-    }
-
-    @Test
-    fun testGetGroupSummary_topLevelEntry() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-        val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
-        assertThat(gmm.getGroupSummary(entry)).isNull()
-    }
-
-    @Test
-    fun testGetGroupSummary_summary() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
-        assertThat(gmm.getGroupSummary(summary)).isEqualTo(summary)
-    }
-
-    @Test
-    fun testGetGroupSummary_child() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
-
-        assertThat(gmm.getGroupSummary(entry)).isEqualTo(summary)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index ff02ef3..b01281c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -42,9 +42,9 @@
 import com.android.internal.logging.InstanceId;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository;
@@ -110,7 +110,8 @@
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
     private NotificationPanelLoggerFake mNotificationPanelLoggerFake =
             new NotificationPanelLoggerFake();
-    private final TestScope mTestScope = TestScopeProvider.getTestScope();
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+    private final TestScope mTestScope = mKosmos.getTestScope();
     private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
     private final PowerInteractor mPowerInteractor =
             PowerInteractorFactory.create().getPowerInteractor();
@@ -133,7 +134,9 @@
                 mKeyguardRepository,
                 mHeadsUpManager,
                 mPowerInteractor,
-                mActiveNotificationsInteractor);
+                mActiveNotificationsInteractor,
+                mKosmos.getFakeSceneContainerFlags(),
+                () -> mKosmos.getSceneInteractor());
         mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
 
         mEntry = new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
index 3f7fc97..fd41921 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
@@ -62,7 +62,7 @@
     fun onCreateView_noMatchingViewForName_returnNull() {
         // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
         val layoutType = FLAG_CONTENT_VIEW_EXPANDED
-        inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+        inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
 
         // WHEN we try to inflate an ImageView for the expanded layout
         val createdView = inflaterFactory.onCreateView("ImageView", context, attrs)
@@ -78,7 +78,7 @@
     fun onCreateView_noMatchingViewForLayoutType_returnNull() {
         // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
         val layoutType = FLAG_CONTENT_VIEW_HEADS_UP
-        inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+        inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
 
         // WHEN we try to inflate a TextView for the heads-up layout
         val createdView = inflaterFactory.onCreateView("TextView", context, attrs)
@@ -94,7 +94,7 @@
     fun onCreateView_matchingViews_returnReplacementView() {
         // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
         val layoutType = FLAG_CONTENT_VIEW_EXPANDED
-        inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+        inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
 
         // WHEN we try to inflate a TextView for the expanded layout
         val createdView = inflaterFactory.onCreateView("TextView", context, attrs)
@@ -110,7 +110,7 @@
         // GIVEN we have two factories that replaces TextViews in expanded layouts
         val layoutType = FLAG_CONTENT_VIEW_EXPANDED
         inflaterFactory =
-            NotifLayoutInflaterFactory(
+            createNotifLayoutInflaterFactory(
                 row,
                 layoutType,
                 setOf(
@@ -147,4 +147,18 @@
                     null
                 }
         }
+
+    private fun createNotifLayoutInflaterFactory(
+        row: ExpandableNotificationRow,
+        layoutType: Int,
+        notifRemoteViewsFactoryContainer: Set<NotifRemoteViewsFactory>
+    ) =
+        NotifLayoutInflaterFactory(
+            row,
+            layoutType,
+            object : NotifRemoteViewsFactoryContainer {
+                override val factories: Set<NotifRemoteViewsFactory> =
+                    notifRemoteViewsFactoryContainer
+            }
+        )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 71613ed..6549193 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -69,9 +69,9 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -124,7 +124,8 @@
     private NotificationChannel mTestNotificationChannel = new NotificationChannel(
             TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
 
-    private TestScope mTestScope = TestScopeProvider.getTestScope();
+    private KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+    private TestScope mTestScope = mKosmos.getTestScope();
     private JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
     private TestableLooper mTestableLooper;
@@ -182,7 +183,10 @@
                 new FakeKeyguardRepository(),
                 mHeadsUpManager,
                 PowerInteractorFactory.create().getPowerInteractor(),
-                mActiveNotificationsInteractor);
+                mActiveNotificationsInteractor,
+                mKosmos.getFakeSceneContainerFlags(),
+                () -> mKosmos.getSceneInteractor()
+        );
 
         mGutsManager = new NotificationGutsManager(
                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
new file mode 100644
index 0000000..446b9d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -0,0 +1,631 @@
+/*
+ * 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.statusbar.notification.row
+
+import android.R
+import android.app.AppOpsManager
+import android.app.INotificationManager
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ShortcutManager
+import android.content.pm.launcherApps
+import android.graphics.Color
+import android.os.Binder
+import android.os.Handler
+import android.os.userManager
+import android.provider.Settings
+import android.service.notification.NotificationListenerService.Ranking
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.util.ArraySet
+import android.view.View
+import android.view.accessibility.accessibilityManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.metricsLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.statusbar.statusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractorFactory.create
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.shade.shadeControllerSceneImpl
+import com.android.systemui.statusbar.NotificationEntryHelper
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.AssistantFeedbackController
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.notificationLockscreenUserManager
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.wmshell.BubblesManager
+import java.util.Optional
+import junit.framework.Assert
+import kotlin.test.assertEquals
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.invocation.InvocationOnMock
+
+/** Tests for [NotificationGutsManager] with the scene container enabled. */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
+    private val testNotificationChannel =
+        NotificationChannel(
+            TEST_CHANNEL_ID,
+            TEST_CHANNEL_ID,
+            NotificationManager.IMPORTANCE_DEFAULT
+        )
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val javaAdapter = JavaAdapter(testScope.backgroundScope)
+    private val executor = FakeExecutor(FakeSystemClock())
+    private lateinit var testableLooper: TestableLooper
+    private lateinit var handler: Handler
+    private lateinit var helper: NotificationTestHelper
+    private lateinit var gutsManager: NotificationGutsManager
+    private lateinit var windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor
+
+    private val metricsLogger = kosmos.metricsLogger
+    private val deviceProvisionedController = kosmos.deviceProvisionedController
+    private val accessibilityManager = kosmos.accessibilityManager
+    private val mBarService = kosmos.statusBarService
+    private val launcherApps = kosmos.launcherApps
+    private val shadeController = kosmos.shadeControllerSceneImpl
+    private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
+    private val statusBarStateController = kosmos.statusBarStateController
+    private val headsUpManager = kosmos.headsUpManager
+    private val activityStarter = kosmos.activityStarter
+    private val userManager = kosmos.userManager
+    private val activeNotificationsInteractor = kosmos.activeNotificationsInteractor
+    private val sceneInteractor = kosmos.sceneInteractor
+
+    @Mock private lateinit var onUserInteractionCallback: OnUserInteractionCallback
+    @Mock private lateinit var presenter: NotificationPresenter
+    @Mock private lateinit var notificationActivityStarter: NotificationActivityStarter
+    @Mock private lateinit var notificationListContainer: NotificationListContainer
+    @Mock
+    private lateinit var onSettingsClickListener: NotificationGutsManager.OnSettingsClickListener
+    @Mock private lateinit var highPriorityProvider: HighPriorityProvider
+    @Mock private lateinit var notificationManager: INotificationManager
+    @Mock private lateinit var shortcutManager: ShortcutManager
+    @Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController
+    @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
+    @Mock private lateinit var contextTracker: UserContextProvider
+    @Mock private lateinit var bubblesManager: BubblesManager
+    @Mock private lateinit var peopleSpaceWidgetManager: PeopleSpaceWidgetManager
+    @Mock private lateinit var assistantFeedbackController: AssistantFeedbackController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        val sceneContainerFlags = kosmos.fakeSceneContainerFlags
+        sceneContainerFlags.enabled = true
+        testableLooper = TestableLooper.get(this)
+        allowTestableLooperAsMainThread()
+        handler = Handler.createAsync(testableLooper.getLooper())
+        helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+        Mockito.`when`(accessibilityManager.isTouchExplorationEnabled).thenReturn(false)
+        windowRootViewVisibilityInteractor =
+            WindowRootViewVisibilityInteractor(
+                testScope.backgroundScope,
+                WindowRootViewVisibilityRepository(mBarService, executor),
+                FakeKeyguardRepository(),
+                headsUpManager,
+                create().powerInteractor,
+                activeNotificationsInteractor,
+                sceneContainerFlags,
+                { sceneInteractor },
+            )
+        gutsManager =
+            NotificationGutsManager(
+                mContext,
+                handler,
+                handler,
+                javaAdapter,
+                accessibilityManager,
+                highPriorityProvider,
+                notificationManager,
+                userManager,
+                peopleSpaceWidgetManager,
+                launcherApps,
+                shortcutManager,
+                channelEditorDialogController,
+                contextTracker,
+                assistantFeedbackController,
+                Optional.of(bubblesManager),
+                UiEventLoggerFake(),
+                onUserInteractionCallback,
+                shadeController,
+                windowRootViewVisibilityInteractor,
+                notificationLockscreenUserManager,
+                statusBarStateController,
+                mBarService,
+                deviceProvisionedController,
+                metricsLogger,
+                headsUpManager,
+                activityStarter
+            )
+        gutsManager.setUpWithPresenter(
+            presenter,
+            notificationListContainer,
+            onSettingsClickListener
+        )
+        gutsManager.setNotificationActivityStarter(notificationActivityStarter)
+        gutsManager.start()
+    }
+
+    @Test
+    fun testOpenAndCloseGuts() {
+        val guts = Mockito.spy(NotificationGuts(mContext))
+        Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock
+            ->
+            handler.post((invocation.arguments[0] as Runnable))
+            null
+        }
+
+        // Test doesn't support animation since the guts view is not attached.
+        Mockito.doNothing()
+            .`when`(guts)
+            .openControls(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.any(Runnable::class.java)
+            )
+        val realRow = createTestNotificationRow()
+        val menuItem = createTestMenuItem(realRow)
+        val row = Mockito.spy(realRow)
+        Mockito.`when`(row!!.windowToken).thenReturn(Binder())
+        Mockito.`when`(row.guts).thenReturn(guts)
+        Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+        assertEquals(View.INVISIBLE.toLong(), guts.visibility.toLong())
+        testableLooper.processAllMessages()
+        verify(guts)
+            .openControls(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.any(Runnable::class.java)
+            )
+        verify(headsUpManager).setGutsShown(realRow!!.entry, true)
+        assertEquals(View.VISIBLE.toLong(), guts.visibility.toLong())
+        gutsManager.closeAndSaveGuts(false, false, true, 0, 0, false)
+        verify(guts)
+            .closeControls(
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyBoolean()
+            )
+        verify(row, Mockito.times(1)).setGutsView(ArgumentMatchers.any())
+        testableLooper.processAllMessages()
+        verify(headsUpManager).setGutsShown(realRow.entry, false)
+    }
+
+    @Test
+    fun testLockscreenShadeVisible_visible_gutsNotClosed() {
+        // First, start out lockscreen or shade as not visible
+        setIsLockscreenOrShadeVisible(false)
+        testScope.testScheduler.runCurrent()
+        val guts = Mockito.mock(NotificationGuts::class.java)
+        gutsManager.exposedGuts = guts
+
+        // WHEN the lockscreen or shade becomes visible
+        setIsLockscreenOrShadeVisible(true)
+        testScope.testScheduler.runCurrent()
+
+        // THEN the guts are not closed
+        verify(guts, Mockito.never()).removeCallbacks(ArgumentMatchers.any())
+        verify(guts, Mockito.never())
+            .closeControls(
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyBoolean()
+            )
+    }
+
+    @Test
+    fun testLockscreenShadeVisible_notVisible_gutsClosed() {
+        // First, start out lockscreen or shade as visible
+        setIsLockscreenOrShadeVisible(true)
+        testScope.testScheduler.runCurrent()
+        val guts = Mockito.mock(NotificationGuts::class.java)
+        gutsManager.exposedGuts = guts
+
+        // WHEN the lockscreen or shade is no longer visible
+        setIsLockscreenOrShadeVisible(false)
+        testScope.testScheduler.runCurrent()
+
+        // THEN the guts are closed
+        verify(guts).removeCallbacks(ArgumentMatchers.any())
+        verify(guts)
+            .closeControls(
+                /* leavebehinds= */ ArgumentMatchers.eq(true),
+                /* controls= */ ArgumentMatchers.eq(true),
+                /* x= */ ArgumentMatchers.anyInt(),
+                /* y= */ ArgumentMatchers.anyInt(),
+                /* force= */ ArgumentMatchers.eq(true)
+            )
+    }
+
+    @Test
+    fun testLockscreenShadeVisible_notVisible_listContainerReset() {
+        // First, start out lockscreen or shade as visible
+        setIsLockscreenOrShadeVisible(true)
+        testScope.testScheduler.runCurrent()
+
+        // WHEN the lockscreen or shade is no longer visible
+        setIsLockscreenOrShadeVisible(false)
+        testScope.testScheduler.runCurrent()
+
+        // THEN the list container is reset
+        verify(notificationListContainer)
+            .resetExposedMenuView(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    fun testChangeDensityOrFontScale() {
+        val guts = Mockito.spy(NotificationGuts(mContext))
+        Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock
+            ->
+            handler.post((invocation.arguments[0] as Runnable))
+            null
+        }
+
+        // Test doesn't support animation since the guts view is not attached.
+        Mockito.doNothing()
+            .`when`(guts)
+            .openControls(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.any(Runnable::class.java)
+            )
+        val realRow = createTestNotificationRow()
+        val menuItem = createTestMenuItem(realRow)
+        val row = Mockito.spy(realRow)
+        Mockito.`when`(row!!.windowToken).thenReturn(Binder())
+        Mockito.`when`(row.guts).thenReturn(guts)
+        Mockito.doNothing().`when`(row).ensureGutsInflated()
+        val realEntry = realRow!!.entry
+        val entry = Mockito.spy(realEntry)
+        Mockito.`when`(entry.row).thenReturn(row)
+        Mockito.`when`(entry.getGuts()).thenReturn(guts)
+        Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+        testableLooper.processAllMessages()
+        verify(guts)
+            .openControls(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.any(Runnable::class.java)
+            )
+
+        // called once by mGutsManager.bindGuts() in mGutsManager.openGuts()
+        verify(row).setGutsView(ArgumentMatchers.any())
+        row.onDensityOrFontScaleChanged()
+        gutsManager.onDensityOrFontScaleChanged(entry)
+        testableLooper.processAllMessages()
+        gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false)
+        verify(guts)
+            .closeControls(
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyBoolean()
+            )
+
+        // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged()
+        verify(row, Mockito.times(2)).setGutsView(ArgumentMatchers.any())
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera() {
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(notificationActivityStarter, Mockito.times(1))
+            .startNotificationGutsIntent(
+                captor.capture(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any()
+            )
+        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_mic() {
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(notificationActivityStarter, Mockito.times(1))
+            .startNotificationGutsIntent(
+                captor.capture(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any()
+            )
+        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera_mic() {
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(notificationActivityStarter, Mockito.times(1))
+            .startNotificationGutsIntent(
+                captor.capture(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any()
+            )
+        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_overlay() {
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(notificationActivityStarter, Mockito.times(1))
+            .startNotificationGutsIntent(
+                captor.capture(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any()
+            )
+        assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.value.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera_mic_overlay() {
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(notificationActivityStarter, Mockito.times(1))
+            .startNotificationGutsIntent(
+                captor.capture(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any()
+            )
+        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera_overlay() {
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(notificationActivityStarter, Mockito.times(1))
+            .startNotificationGutsIntent(
+                captor.capture(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any()
+            )
+        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_mic_overlay() {
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(notificationActivityStarter, Mockito.times(1))
+            .startNotificationGutsIntent(
+                captor.capture(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any()
+            )
+        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun testInitializeNotificationInfoView_highPriority() {
+        val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
+        val row = Mockito.spy(helper.createRow())
+        val entry = row.entry
+        NotificationEntryHelper.modifyRanking(entry)
+            .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+            .setImportance(NotificationManager.IMPORTANCE_HIGH)
+            .build()
+        Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+        Mockito.`when`(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
+        val statusBarNotification = entry.sbn
+        gutsManager.initializeNotificationInfo(row, notificationInfoView)
+        verify(notificationInfoView)
+            .bindNotification(
+                ArgumentMatchers.any(PackageManager::class.java),
+                ArgumentMatchers.any(INotificationManager::class.java),
+                ArgumentMatchers.eq(onUserInteractionCallback),
+                ArgumentMatchers.eq(channelEditorDialogController),
+                ArgumentMatchers.eq(statusBarNotification.packageName),
+                ArgumentMatchers.any(NotificationChannel::class.java),
+                ArgumentMatchers.eq(entry),
+                ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
+                ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
+                ArgumentMatchers.any(UiEventLogger::class.java),
+                ArgumentMatchers.eq(true),
+                ArgumentMatchers.eq(false),
+                ArgumentMatchers.eq(true), /* wasShownHighPriority */
+                ArgumentMatchers.eq(assistantFeedbackController),
+                ArgumentMatchers.any(MetricsLogger::class.java)
+            )
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun testInitializeNotificationInfoView_PassesAlongProvisionedState() {
+        val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
+        val row = Mockito.spy(helper.createRow())
+        NotificationEntryHelper.modifyRanking(row.entry)
+            .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+            .build()
+        Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+        val statusBarNotification = row.entry.sbn
+        val entry = row.entry
+        gutsManager.initializeNotificationInfo(row, notificationInfoView)
+        verify(notificationInfoView)
+            .bindNotification(
+                ArgumentMatchers.any(PackageManager::class.java),
+                ArgumentMatchers.any(INotificationManager::class.java),
+                ArgumentMatchers.eq(onUserInteractionCallback),
+                ArgumentMatchers.eq(channelEditorDialogController),
+                ArgumentMatchers.eq(statusBarNotification.packageName),
+                ArgumentMatchers.any(NotificationChannel::class.java),
+                ArgumentMatchers.eq(entry),
+                ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
+                ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
+                ArgumentMatchers.any(UiEventLogger::class.java),
+                ArgumentMatchers.eq(true),
+                ArgumentMatchers.eq(false),
+                ArgumentMatchers.eq(false), /* wasShownHighPriority */
+                ArgumentMatchers.eq(assistantFeedbackController),
+                ArgumentMatchers.any(MetricsLogger::class.java)
+            )
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun testInitializeNotificationInfoView_withInitialAction() {
+        val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
+        val row = Mockito.spy(helper.createRow())
+        NotificationEntryHelper.modifyRanking(row.entry)
+            .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+            .build()
+        Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+        val statusBarNotification = row.entry.sbn
+        val entry = row.entry
+        gutsManager.initializeNotificationInfo(row, notificationInfoView)
+        verify(notificationInfoView)
+            .bindNotification(
+                ArgumentMatchers.any(PackageManager::class.java),
+                ArgumentMatchers.any(INotificationManager::class.java),
+                ArgumentMatchers.eq(onUserInteractionCallback),
+                ArgumentMatchers.eq(channelEditorDialogController),
+                ArgumentMatchers.eq(statusBarNotification.packageName),
+                ArgumentMatchers.any(NotificationChannel::class.java),
+                ArgumentMatchers.eq(entry),
+                ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
+                ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
+                ArgumentMatchers.any(UiEventLogger::class.java),
+                ArgumentMatchers.eq(true),
+                ArgumentMatchers.eq(false),
+                ArgumentMatchers.eq(false), /* wasShownHighPriority */
+                ArgumentMatchers.eq(assistantFeedbackController),
+                ArgumentMatchers.any(MetricsLogger::class.java)
+            )
+    }
+
+    private fun createTestNotificationRow(): ExpandableNotificationRow? {
+        val nb =
+            Notification.Builder(mContext, testNotificationChannel.id)
+                .setContentTitle("foo")
+                .setColorized(true)
+                .setColor(Color.RED)
+                .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+                .setSmallIcon(R.drawable.sym_def_app_icon)
+        return try {
+            val row = helper.createRow(nb.build())
+            NotificationEntryHelper.modifyRanking(row.entry)
+                .setChannel(testNotificationChannel)
+                .build()
+            row
+        } catch (e: Exception) {
+            org.junit.Assert.fail()
+            null
+        }
+    }
+
+    private fun setIsLockscreenOrShadeVisible(isVisible: Boolean) {
+        val key =
+            if (isVisible) {
+                SceneKey.Lockscreen
+            } else {
+                SceneKey.Bouncer
+            }
+        sceneInteractor.changeScene(SceneModel(key), "test")
+        sceneInteractor.setTransitionState(
+            MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+        )
+        testScope.runCurrent()
+    }
+
+    private fun createTestMenuItem(
+        row: ExpandableNotificationRow?
+    ): NotificationMenuRowPlugin.MenuItem {
+        val menuRow: NotificationMenuRowPlugin =
+            NotificationMenuRow(mContext, peopleNotificationIdentifier)
+        menuRow.createMenu(row, row!!.entry.sbn)
+        val menuItem = menuRow.getLongpressMenuItem(mContext)
+        Assert.assertNotNull(menuItem)
+        return menuItem
+    }
+
+    companion object {
+        private const val TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index b58a41c..457acd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -190,7 +190,7 @@
 
         mWakefulnessLifecycle.dispatchFinishedWakingUp();
         mThemeOverlayController.start();
-        verify(mUserTracker).addCallback(mUserTrackerCallback.capture(), eq(mMainExecutor));
+        verify(mUserTracker).addCallback(mUserTrackerCallback.capture(), eq(mBgExecutor));
         verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null),
                 eq(UserHandle.USER_ALL));
         verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiver.capture(), any(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index 20fa545..cccd908 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -50,4 +50,11 @@
     fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) {
         _isCommunalHubShowing.value = isCommunalHubShowing
     }
+
+    private val _communalEnabledState: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val communalEnabledState: StateFlow<Boolean> = _communalEnabledState
+
+    fun setCommunalEnabledState(enabled: Boolean) {
+        _communalEnabledState.value = enabled
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index 7cc5d6b..e13fa52 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -14,9 +14,66 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.shade
 
+import android.view.WindowManager
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.windowRootViewVisibilityInteractor
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-var Kosmos.shadeController by Kosmos.Fixture { mock<ShadeController>() }
+val Kosmos.shadeControllerSceneImpl by
+    Kosmos.Fixture {
+        ShadeControllerSceneImpl(
+            scope = applicationCoroutineScope,
+            shadeInteractor = shadeInteractor,
+            sceneInteractor = sceneInteractor,
+            notificationStackScrollLayout = mock<NotificationStackScrollLayout>(),
+            deviceEntryInteractor = deviceEntryInteractor,
+            touchLog = mock<LogBuffer>(),
+            commandQueue = mock<CommandQueue>(),
+            statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>(),
+            notificationShadeWindowController = mock<NotificationShadeWindowController>(),
+            assistManagerLazy = { mock<AssistManager>() },
+        )
+    }
+
+val Kosmos.shadeControllerImpl by
+    Kosmos.Fixture {
+        ShadeControllerImpl(
+            mock<CommandQueue>(),
+            fakeExecutor,
+            mock<LogBuffer>(),
+            windowRootViewVisibilityInteractor,
+            mock<KeyguardStateController>(),
+            statusBarStateController,
+            statusBarKeyguardViewManager,
+            mock<StatusBarWindowController>(),
+            deviceProvisionedController,
+            mock<NotificationShadeWindowController>(),
+            mock<WindowManager>(),
+            { mock<ShadeViewController>() },
+            { mock<AssistManager>() },
+            { mock<NotificationGutsManager>() },
+        )
+    }
+var Kosmos.shadeController: ShadeController by Kosmos.Fixture { shadeControllerImpl }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
similarity index 90%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
index dda7fad..4efcada 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
@@ -52,32 +52,38 @@
         return ge;
     }
 
+    /** Sets the group key. */
     public GroupEntryBuilder setKey(String key) {
         mKey = key;
         return this;
     }
 
+    /** Sets the creation time. */
     public GroupEntryBuilder setCreationTime(long creationTime) {
         mCreationTime = creationTime;
         return this;
     }
 
+    /** Sets the parent entry of the group. */
     public GroupEntryBuilder setParent(@Nullable GroupEntry entry) {
         mParent = entry;
         return this;
     }
 
+    /** Sets the section the group belongs to. */
     public GroupEntryBuilder setSection(@Nullable NotifSection section) {
         mNotifSection = section;
         return this;
     }
 
+    /** Sets the group summary. */
     public GroupEntryBuilder setSummary(
             NotificationEntry summary) {
         mSummary = summary;
         return this;
     }
 
+    /** Sets the group children. */
     public GroupEntryBuilder setChildren(List<NotificationEntry> children) {
         mChildren.clear();
         mChildren.addAll(children);
@@ -90,6 +96,7 @@
         return this;
     }
 
+    /** Get the group's internal children list. */
     public static List<NotificationEntry> getRawChildren(GroupEntry groupEntry) {
         return groupEntry.getRawChildren();
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
index d80ee75..cf800d0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
@@ -23,6 +23,8 @@
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.policy.headsUpManager
 
@@ -34,5 +36,7 @@
         headsUpManager = headsUpManager,
         powerInteractor = powerInteractor,
         activeNotificationsInteractor = activeNotificationsInteractor,
+        sceneInteractorProvider = { sceneInteractor },
+        sceneContainerFlags = sceneContainerFlags,
     )
 }
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index f5e4af5..e33fff1 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -6,6 +6,9 @@
 # Keep all feature flag implementations
 class :feature_flags stubclass
 
+# Keep all sysprops generated code implementations
+class :sysprops stubclass
+
 # Collections
 class android.util.ArrayMap stubclass
 class android.util.ArraySet stubclass
@@ -112,6 +115,12 @@
 class android.os.PatternMatcher stubclass
 class android.os.ParcelUuid stubclass
 
+# Logging related interfaces from modules-utils
+class com.android.internal.logging.InstanceId stubclass
+class com.android.internal.logging.InstanceIdSequence stubclass
+class com.android.internal.logging.UiEvent stubclass
+class com.android.internal.logging.UiEventLogger stubclass
+
 # XML
 class com.android.internal.util.XmlPullParserWrapper stubclass
 class com.android.internal.util.XmlSerializerWrapper stubclass
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index eacdc2f..91c522e 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -19,8 +19,6 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 
-import java.util.Objects;
-
 public class RavenwoodRuleImpl {
     private static final String MAIN_THREAD_NAME = "RavenwoodMain";
 
@@ -31,6 +29,10 @@
     public static void init(RavenwoodRule rule) {
         android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
         android.os.Binder.init$ravenwood();
+        android.os.SystemProperties.init$ravenwood(
+                rule.mSystemProperties.getValues(),
+                rule.mSystemProperties.getKeyReadablePredicate(),
+                rule.mSystemProperties.getKeyWritablePredicate());
 
         com.android.server.LocalServices.removeAllServicesForTest();
 
@@ -49,7 +51,8 @@
 
         com.android.server.LocalServices.removeAllServicesForTest();
 
-        android.os.Process.reset$ravenwood();
+        android.os.SystemProperties.reset$ravenwood();
         android.os.Binder.reset$ravenwood();
+        android.os.Process.reset$ravenwood();
     }
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 53da8ba..dd442f0 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -62,6 +62,8 @@
 
     boolean mProvideMainThread = false;
 
+    final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
+
     public RavenwoodRule() {
     }
 
@@ -98,6 +100,40 @@
             return this;
         }
 
+        /**
+         * Configure the given system property as immutable for the duration of the test.
+         * Read access to the key is allowed, and write access will fail. When {@code value} is
+         * {@code null}, the value is left as undefined.
+         *
+         * All properties in the {@code debug.*} namespace are automatically mutable, with no
+         * developer action required.
+         *
+         * Has no effect under non-Ravenwood environments.
+         */
+        public Builder setSystemPropertyImmutable(/* @NonNull */ String key,
+                /* @Nullable */ Object value) {
+            mRule.mSystemProperties.setValue(key, value);
+            mRule.mSystemProperties.setAccessReadOnly(key);
+            return this;
+        }
+
+        /**
+         * Configure the given system property as mutable for the duration of the test.
+         * Both read and write access to the key is allowed, and its value will be reset between
+         * each test. When {@code value} is {@code null}, the value is left as undefined.
+         *
+         * All properties in the {@code debug.*} namespace are automatically mutable, with no
+         * developer action required.
+         *
+         * Has no effect under non-Ravenwood environments.
+         */
+        public Builder setSystemPropertyMutable(/* @NonNull */ String key,
+                /* @Nullable */ Object value) {
+            mRule.mSystemProperties.setValue(key, value);
+            mRule.mSystemProperties.setAccessReadWrite(key);
+            return this;
+        }
+
         public RavenwoodRule build() {
             return mRule;
         }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
new file mode 100644
index 0000000..85ad4e4
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -0,0 +1,175 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+class RavenwoodSystemProperties {
+    private final Map<String, String> mValues = new HashMap<>();
+
+    /** Set of additional keys that should be considered readable */
+    private final Set<String> mKeyReadable = new HashSet<>();
+    private final Predicate<String> mKeyReadablePredicate = (key) -> {
+        final String root = getKeyRoot(key);
+
+        if (root.startsWith("debug.")) return true;
+
+        // This set is carefully curated to help identify situations where a test may
+        // accidentally depend on a default value of an obscure property whose owner hasn't
+        // decided how Ravenwood should behave.
+        if (root.startsWith("boot.")) return true;
+        if (root.startsWith("build.")) return true;
+        if (root.startsWith("product.")) return true;
+        if (root.startsWith("soc.")) return true;
+        if (root.startsWith("system.")) return true;
+
+        switch (key) {
+            case "gsm.version.baseband":
+            case "no.such.thing":
+            case "ro.bootloader":
+            case "ro.debuggable":
+            case "ro.hardware":
+            case "ro.hw_timeout_multiplier":
+            case "ro.odm.build.media_performance_class":
+            case "ro.treble.enabled":
+            case "ro.vndk.version":
+                return true;
+        }
+
+        return mKeyReadable.contains(key);
+    };
+
+    /** Set of additional keys that should be considered writable */
+    private final Set<String> mKeyWritable = new HashSet<>();
+    private final Predicate<String> mKeyWritablePredicate = (key) -> {
+        final String root = getKeyRoot(key);
+
+        if (root.startsWith("debug.")) return true;
+
+        return mKeyWritable.contains(key);
+    };
+
+    public RavenwoodSystemProperties() {
+        // TODO: load these values from build.prop generated files
+        setValueForPartitions("product.brand", "Android");
+        setValueForPartitions("product.device", "Ravenwood");
+        setValueForPartitions("product.manufacturer", "Android");
+        setValueForPartitions("product.model", "Ravenwood");
+        setValueForPartitions("product.name", "Ravenwood");
+
+        setValueForPartitions("product.cpu.abilist", "x86_64");
+        setValueForPartitions("product.cpu.abilist32", "");
+        setValueForPartitions("product.cpu.abilist64", "x86_64");
+
+        setValueForPartitions("build.date", "Thu Jan 01 00:00:00 GMT 2024");
+        setValueForPartitions("build.date.utc", "1704092400");
+        setValueForPartitions("build.id", "MAIN");
+        setValueForPartitions("build.tags", "dev-keys");
+        setValueForPartitions("build.type", "userdebug");
+        setValueForPartitions("build.version.all_codenames", "REL");
+        setValueForPartitions("build.version.codename", "REL");
+        setValueForPartitions("build.version.incremental", "userdebug.ravenwood.20240101");
+        setValueForPartitions("build.version.known_codenames", "REL");
+        setValueForPartitions("build.version.release", "14");
+        setValueForPartitions("build.version.release_or_codename", "VanillaIceCream");
+        setValueForPartitions("build.version.sdk", "34");
+
+        setValue("ro.board.first_api_level", "1");
+        setValue("ro.product.first_api_level", "1");
+
+        setValue("ro.soc.manufacturer", "Android");
+        setValue("ro.soc.model", "Ravenwood");
+
+        setValue("ro.debuggable", "1");
+    }
+
+    Map<String, String> getValues() {
+        return new HashMap<>(mValues);
+    }
+
+    Predicate<String> getKeyReadablePredicate() {
+        return mKeyReadablePredicate;
+    }
+
+    Predicate<String> getKeyWritablePredicate() {
+        return mKeyWritablePredicate;
+    }
+
+    private static final String[] PARTITIONS = {
+            "bootimage",
+            "odm",
+            "product",
+            "system",
+            "system_ext",
+            "vendor",
+            "vendor_dlkm",
+    };
+
+    /**
+     * Set the given property for all possible partitions where it could be defined. For
+     * example, the value of {@code ro.build.type} is typically also mirrored under
+     * {@code ro.system.build.type}, etc.
+     */
+    private void setValueForPartitions(String key, String value) {
+        setValue("ro." + key, value);
+        for (String partition : PARTITIONS) {
+            setValue("ro." + partition + "." + key, value);
+        }
+    }
+
+    public void setValue(String key, Object value) {
+        final String valueString = (value == null) ? null : String.valueOf(value);
+        if ((valueString == null) || valueString.isEmpty()) {
+            mValues.remove(key);
+        } else {
+            mValues.put(key, valueString);
+        }
+    }
+
+    public void setAccessNone(String key) {
+        mKeyReadable.remove(key);
+        mKeyWritable.remove(key);
+    }
+
+    public void setAccessReadOnly(String key) {
+        mKeyReadable.add(key);
+        mKeyWritable.remove(key);
+    }
+
+    public void setAccessReadWrite(String key) {
+        mKeyReadable.add(key);
+        mKeyWritable.add(key);
+    }
+
+    /**
+     * Return the "root" of the given property key, stripping away any modifier prefix such as
+     * {@code ro.} or {@code persist.}.
+     */
+    private static String getKeyRoot(String key) {
+        if (key.startsWith("ro.")) {
+            return key.substring(3);
+        } else if (key.startsWith("persist.")) {
+            return key.substring(8);
+        } else {
+            return key;
+        }
+    }
+}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index ab2546b..5700f00 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -1,6 +1,9 @@
 # Only classes listed here can use the Ravenwood annotations.
 
 com.android.internal.util.ArrayUtils
+com.android.internal.logging.MetricsLogger
+com.android.internal.logging.testing.FakeMetricsLogger
+com.android.internal.logging.testing.UiEventLoggerFake
 com.android.internal.os.BatteryStatsHistory
 com.android.internal.os.BatteryStatsHistory$TraceDelegate
 com.android.internal.os.BatteryStatsHistory$VarintParceler
@@ -47,6 +50,7 @@
 android.os.Binder
 android.os.Binder$IdentitySupplier
 android.os.Broadcaster
+android.os.Build
 android.os.BundleMerger
 android.os.ConditionVariable
 android.os.FileUtils
@@ -65,8 +69,10 @@
 android.os.Process
 android.os.ServiceSpecificException
 android.os.SystemClock
+android.os.SystemProperties
 android.os.ThreadLocalWorkSource
 android.os.TimestampedValue
+android.os.Trace
 android.os.UidBatteryConsumer
 android.os.UidBatteryConsumer$Builder
 android.os.UserHandle
@@ -126,6 +132,8 @@
 
 android.content.ContentProvider
 
+android.metrics.LogMaker
+
 com.android.server.LocalServices
 com.android.server.power.stats.BatteryStatsImpl
 
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 993b254..44682e2 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -45,6 +45,13 @@
 }
 
 flag {
+    name: "fix_drag_pointer_when_ending_drag"
+    namespace: "accessibility"
+    description: "Send the correct pointer id when transitioning from dragging to delegating states."
+    bug: "300002193"
+}
+
+flag {
     name: "pinch_zoom_zero_min_span"
     namespace: "accessibility"
     description: "Whether to set min span of ScaleGestureDetector to zero."
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index c418485..3086ce1 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -1466,8 +1466,11 @@
             int policyFlags = mState.getLastReceivedPolicyFlags();
             if (mState.isDragging()) {
                 // Send an event to the end of the drag gesture.
-                mDispatcher.sendMotionEvent(
-                        event, ACTION_UP, rawEvent, ALL_POINTER_ID_BITS, policyFlags);
+                int pointerIdBits = ALL_POINTER_ID_BITS;
+                if (Flags.fixDragPointerWhenEndingDrag()) {
+                    pointerIdBits = 1 << mDraggingPointerId;
+                }
+                mDispatcher.sendMotionEvent(event, ACTION_UP, rawEvent, pointerIdBits, policyFlags);
             }
             mState.startDelegating();
             // Deliver all pointers to the view hierarchy.
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index b6e1140..2168cb2 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -583,6 +583,11 @@
             return associationInfo == null ? null : associationInfo.getDisplayName();
         }
 
+        @Override // Binder call
+        public @NonNull List<String> getAllPersistentDeviceIds() {
+            return new ArrayList<>(mLocalService.getAllPersistentDeviceIds());
+        }
+
         // Binder call
         @Override
         public boolean isValidVirtualDeviceId(int deviceId) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9375fb1..a54a48a 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -214,6 +214,7 @@
         "backup_flags_lib",
         "policy_flags_lib",
         "net_flags_lib",
+        "stats_flags_lib",
     ],
     javac_shard_size: 50,
     javacflags: [
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 52a5408..ca04e41 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -472,6 +472,8 @@
 import com.android.server.pm.snapshot.PackageDataSnapshot;
 import com.android.server.power.stats.BatteryStatsImpl;
 import com.android.server.sdksandbox.SdkSandboxManagerLocal;
+import com.android.server.stats.pull.StatsPullAtomService;
+import com.android.server.stats.pull.StatsPullAtomServiceInternal;
 import com.android.server.uri.GrantUri;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.uri.UriGrantsManagerInternal;
@@ -1308,6 +1310,8 @@
      */
     final BatteryStatsService mBatteryStatsService;
 
+    StatsPullAtomServiceInternal mStatsPullAtomServiceInternal;
+
     /**
      * Information about component usage
      */
@@ -16554,6 +16558,21 @@
                 final @ProcessCapability int capability) {
         mBatteryStatsService.noteUidProcessState(uid, state);
         mAppOpsService.updateUidProcState(uid, state, capability);
+        if (StatsPullAtomService.ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+            try {
+                if (mStatsPullAtomServiceInternal == null) {
+                    mStatsPullAtomServiceInternal = LocalServices.getService(
+                            StatsPullAtomServiceInternal.class);
+                }
+                if (mStatsPullAtomServiceInternal != null) {
+                    mStatsPullAtomServiceInternal.noteUidProcessState(uid, state);
+                } else {
+                    Slog.d(TAG, "StatsPullAtomService not ready yet");
+                }
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception during logging uid proc state change event", e);
+            }
+        }
         if (mTrackingAssociations) {
             for (int i1=0, N1=mAssociations.size(); i1<N1; i1++) {
                 ArrayMap<ComponentName, SparseArray<ArrayMap<String, Association>>> targetComponents
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 57c52c2..45f657d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -3754,6 +3754,11 @@
         }
 
         @Override
+        public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+                String processName) {
+        }
+
+        @Override
         public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
         }
 
diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
index 1f98aba..fb89b8e 100644
--- a/services/core/java/com/android/server/am/AppFGSTracker.java
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -102,6 +102,11 @@
         }
 
         @Override
+        public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+                String processName) {
+        }
+
+        @Override
         public void onProcessDied(int pid, int uid) {
         }
     };
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index fa5dbd2..f5c34a5 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2852,6 +2852,7 @@
                         ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
             }
         }
+        dispatchProcessStarted(app, pid);
         checkSlow(app.getStartTime(), "startProcess: done updating pids map");
         return true;
     }
@@ -4977,6 +4978,22 @@
         }
     }
 
+    void dispatchProcessStarted(ProcessRecord app, int pid) {
+        int i = mProcessObservers.beginBroadcast();
+        while (i > 0) {
+            i--;
+            final IProcessObserver observer = mProcessObservers.getBroadcastItem(i);
+            if (observer != null) {
+                try {
+                    observer.onProcessStarted(pid, app.uid, app.info.uid,
+                            app.info.packageName, app.processName);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+        mProcessObservers.finishBroadcast();
+    }
+
     void dispatchProcessDied(int pid, int uid) {
         int i = mProcessObservers.beginBroadcast();
         while (i > 0) {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index dc14c7a..7aafda5 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -153,6 +153,7 @@
         "machine_learning",
         "mainline_modularization",
         "mainline_sdk",
+        "make_pixel_haptics",
         "media_audio",
         "media_drm",
         "media_reliability",
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 684d6a0..cdd147a 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -177,6 +177,11 @@
         }
 
         @Override
+        public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+                String processName) {
+        }
+
+        @Override
         public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
         }
     };
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 99b45ec..cd295b5 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1047,11 +1047,9 @@
     private void initAudioHalBluetoothState() {
         synchronized (mBluetoothAudioStateLock) {
             mBluetoothScoOnApplied = false;
-            AudioSystem.setParameters("BT_SCO=off");
             mBluetoothA2dpSuspendedApplied = false;
-            AudioSystem.setParameters("A2dpSuspended=false");
             mBluetoothLeSuspendedApplied = false;
-            AudioSystem.setParameters("LeAudioSuspended=false");
+            reapplyAudioHalBluetoothState();
         }
     }
 
@@ -1114,6 +1112,34 @@
         }
     }
 
+    @GuardedBy("mBluetoothAudioStateLock")
+    private void reapplyAudioHalBluetoothState() {
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "reapplyAudioHalBluetoothState() mBluetoothScoOnApplied: "
+                    + mBluetoothScoOnApplied + ", mBluetoothA2dpSuspendedApplied: "
+                    + mBluetoothA2dpSuspendedApplied + ", mBluetoothLeSuspendedApplied: "
+                    + mBluetoothLeSuspendedApplied);
+        }
+        // Note: the order of parameters is important.
+        if (mBluetoothScoOnApplied) {
+            AudioSystem.setParameters("A2dpSuspended=true");
+            AudioSystem.setParameters("LeAudioSuspended=true");
+            AudioSystem.setParameters("BT_SCO=on");
+        } else {
+            AudioSystem.setParameters("BT_SCO=off");
+            if (mBluetoothA2dpSuspendedApplied) {
+                AudioSystem.setParameters("A2dpSuspended=true");
+            } else {
+                AudioSystem.setParameters("A2dpSuspended=false");
+            }
+            if (mBluetoothLeSuspendedApplied) {
+                AudioSystem.setParameters("LeAudioSuspended=true");
+            } else {
+                AudioSystem.setParameters("LeAudioSuspended=false");
+            }
+        }
+    }
+
     /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
         if (AudioService.DEBUG_COMM_RTE) {
             Log.v(TAG, "setBluetoothScoOn: " + on + " " + eventSource);
@@ -1775,6 +1801,9 @@
                             initRoutingStrategyIds();
                             updateActiveCommunicationDevice();
                             mDeviceInventory.onRestoreDevices();
+                            synchronized (mBluetoothAudioStateLock) {
+                                reapplyAudioHalBluetoothState();
+                            }
                             mBtHelper.onAudioServerDiedRestoreA2dp();
                             updateCommunicationRoute("MSG_RESTORE_DEVICES");
                         }
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 8fd2ee2..21e6bac 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -439,6 +439,10 @@
             if (fingerprintService != null) {
                 fingerprintService.registerAuthenticationStateListener(listener);
             }
+            final IFaceService faceService = mInjector.getFaceService();
+            if (faceService != null) {
+                faceService.registerAuthenticationStateListener(listener);
+            }
         }
 
         @Override
@@ -449,6 +453,10 @@
             if (fingerprintService != null) {
                 fingerprintService.unregisterAuthenticationStateListener(listener);
             }
+            final IFaceService faceService = mInjector.getFaceService();
+            if (faceService != null) {
+                faceService.unregisterAuthenticationStateListener(listener);
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
index 5863535..1ae4d64 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
@@ -91,6 +91,40 @@
         }
     }
 
+    /**
+     * Defines behavior in response to a successful authentication
+     * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+     *                      authentication
+     * @param userId The user Id for the requested authentication
+     */
+    public void onAuthenticationSucceeded(int requestReason, int userId) {
+        for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+            try {
+                listener.onAuthenticationSucceeded(requestReason, userId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in notifying listener that authentication "
+                        + "succeeded", e);
+            }
+        }
+    }
+
+    /**
+     * Defines behavior in response to a failed authentication
+     * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+     *                      authentication
+     * @param userId The user Id for the requested authentication
+     */
+    public void onAuthenticationFailed(int requestReason, int userId) {
+        for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+            try {
+                listener.onAuthenticationFailed(requestReason, userId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in notifying listener that authentication "
+                        + "failed", e);
+            }
+        }
+    }
+
     @Override
     public void binderDied() {
         // Do nothing, handled below
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 73f3999..321e951 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -24,6 +24,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
+import android.hardware.biometrics.AuthenticationStateListener;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricService;
@@ -63,6 +64,7 @@
 import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -99,6 +101,8 @@
     private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
             mBiometricStateCallback;
     @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
+    @NonNull
     private final FaceProviderFunction mFaceProviderFunction;
     @NonNull private final Function<String, FaceProvider> mFaceProvider;
     @NonNull
@@ -695,7 +699,8 @@
             for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
                 providers.add(
                         Face10.newInstance(getContext(), mBiometricStateCallback,
-                                hidlSensor, mLockoutResetDispatcher));
+                                mAuthenticationStateListeners, hidlSensor,
+                                mLockoutResetDispatcher));
             }
 
             return providers;
@@ -830,6 +835,24 @@
         public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
             mBiometricStateCallback.registerBiometricStateListener(listener);
         }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override
+        public void registerAuthenticationStateListener(
+                @NonNull AuthenticationStateListener listener) {
+            super.registerAuthenticationStateListener_enforcePermission();
+
+            mAuthenticationStateListeners.registerAuthenticationStateListener(listener);
+        }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override
+        public void unregisterAuthenticationStateListener(
+                @NonNull AuthenticationStateListener listener) {
+            super.unregisterAuthenticationStateListener_enforcePermission();
+
+            mAuthenticationStateListeners.unregisterAuthenticationStateListener(listener);
+        }
     }
 
     public FaceService(Context context) {
@@ -848,6 +871,7 @@
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
         mLockPatternUtils = new LockPatternUtils(context);
         mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
+        mAuthenticationStateListeners = new AuthenticationStateListeners();
         mRegistry = new FaceServiceRegistry(mServiceWrapper, biometricServiceSupplier);
         mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
             @Override
@@ -868,8 +892,8 @@
             try {
                 final SensorProps[] props = face.getSensorProps();
                 return new FaceProvider(getContext(),
-                        mBiometricStateCallback, props, name, mLockoutResetDispatcher,
-                        BiometricContext.getInstance(getContext()),
+                        mBiometricStateCallback, mAuthenticationStateListeners, props, name,
+                        mLockoutResetDispatcher, BiometricContext.getInstance(getContext()),
                         false /* resetLockoutRequiresChallenge */);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -881,7 +905,7 @@
         if (Flags.deHidl()) {
             mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
                     ((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
-                            getContext(), mBiometricStateCallback,
+                            getContext(), mBiometricStateCallback, mAuthenticationStateListeners,
                             filteredSensorProps.second,
                             filteredSensorProps.first, mLockoutResetDispatcher,
                             BiometricContext.getInstance(getContext()),
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 22e399c..f35de93 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.NotificationManager;
@@ -44,6 +46,7 @@
 import com.android.server.biometrics.log.OperationContextExt;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
@@ -77,6 +80,8 @@
     private ICancellationSignal mCancellationSignal;
     @Nullable
     private final SensorPrivacyManager mSensorPrivacyManager;
+    @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
     @FaceManager.FaceAcquired
     private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
 
@@ -89,11 +94,13 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
             @NonNull LockoutTracker lockoutCache, boolean allowBackgroundAuthentication,
-            @Authenticators.Types int sensorStrength) {
+            @Authenticators.Types int sensorStrength,
+            @NonNull AuthenticationStateListeners authenticationStateListeners) {
         this(context, lazyDaemon, token, requestId, listener, operationId,
                 restricted, options, cookie, requireConfirmation, logger, biometricContext,
                 isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication,
-                context.getSystemService(SensorPrivacyManager.class), sensorStrength);
+                context.getSystemService(SensorPrivacyManager.class), sensorStrength,
+                authenticationStateListeners);
     }
 
     @VisibleForTesting
@@ -107,7 +114,8 @@
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
             @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
             SensorPrivacyManager sensorPrivacyManager,
-            @Authenticators.Types int biometricStrength) {
+            @Authenticators.Types int biometricStrength,
+            @NonNull AuthenticationStateListeners authenticationStateListeners) {
         super(context, lazyDaemon, token, listener, operationId, restricted,
                 options, cookie, requireConfirmation, logger, biometricContext,
                 isStrongBiometric, null /* taskStackListener */, lockoutTracker,
@@ -118,6 +126,7 @@
         mNotificationManager = context.getSystemService(NotificationManager.class);
         mSensorPrivacyManager = sensorPrivacyManager;
         mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
+        mAuthenticationStateListeners = authenticationStateListeners;
 
         final Resources resources = getContext().getResources();
         mBiometricPromptIgnoreList = resources.getIntArray(
@@ -262,6 +271,16 @@
                 0 /* error */,
                 0 /* vendorError */,
                 getTargetUserId()));
+
+        if (reportBiometricAuthAttempts()) {
+            if (authenticated) {
+                mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+                        getTargetUserId());
+            } else {
+                mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+                        getTargetUserId());
+            }
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index e4ecf1a..d01c268 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -59,6 +59,7 @@
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -103,6 +104,8 @@
     @NonNull
     private final BiometricStateCallback mBiometricStateCallback;
     @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
+    @NonNull
     private final String mHalInstanceName;
     @NonNull
     private final Handler mHandler;
@@ -156,18 +159,20 @@
 
     public FaceProvider(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull SensorProps[] props,
             @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull BiometricContext biometricContext,
             boolean resetLockoutRequiresChallenge) {
-        this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
-                biometricContext, null /* daemon */, getHandler(), resetLockoutRequiresChallenge,
-                false /* testHalEnabled */);
+        this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
+                lockoutResetDispatcher, biometricContext, null /* daemon */, getHandler(),
+                resetLockoutRequiresChallenge, false /* testHalEnabled */);
     }
 
     @VisibleForTesting FaceProvider(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull SensorProps[] props,
             @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
@@ -178,6 +183,7 @@
             boolean testHalEnabled) {
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
+        mAuthenticationStateListeners = authenticationStateListeners;
         mHalInstanceName = halInstanceName;
         mFaceSensors = new SensorList<>(ActivityManager.getService());
         if (Flags.deHidl()) {
@@ -610,7 +616,8 @@
                             mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
                     mUsageStats, lockoutTracker,
-                    allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
+                    allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId),
+                    mAuthenticationStateListeners);
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
                 @Override
                 public void onClientStarted(
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 5337666..48a676c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -64,6 +64,7 @@
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -119,6 +120,8 @@
 
     @NonNull private final FaceSensorPropertiesInternal mSensorProperties;
     @NonNull private final BiometricStateCallback mBiometricStateCallback;
+    @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
     @NonNull private final Context mContext;
     @NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler;
     @NonNull private final Handler mHandler;
@@ -350,6 +353,7 @@
     @VisibleForTesting
     Face10(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull FaceSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull Handler handler,
@@ -358,6 +362,7 @@
         mSensorProperties = sensorProps;
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
+        mAuthenticationStateListeners = authenticationStateListeners;
         mSensorId = sensorProps.sensorId;
         mScheduler = scheduler;
         mHandler = handler;
@@ -392,11 +397,12 @@
 
     public static Face10 newInstance(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull FaceSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
         final Handler handler = new Handler(Looper.getMainLooper());
-        return new Face10(context, biometricStateCallback, sensorProps, lockoutResetDispatcher,
-                handler, new BiometricScheduler<>(
+        return new Face10(context, biometricStateCallback, authenticationStateListeners,
+                sensorProps, lockoutResetDispatcher, handler, new BiometricScheduler<>(
                         BiometricScheduler.SENSOR_TYPE_FACE,
                         null /* gestureAvailabilityTracker */),
                 BiometricContext.getInstance(context));
@@ -846,7 +852,8 @@
                         createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
                                 mAuthenticationStatsCollector), mBiometricContext,
                         isStrongBiometric, mUsageStats, mLockoutTracker,
-                        allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+                        allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
+                        mAuthenticationStateListeners);
         mScheduler.scheduleClientMonitor(client);
     }
 
@@ -860,7 +867,8 @@
                 createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
                         mAuthenticationStatsCollector), mBiometricContext,
                 isStrongBiometric, mLockoutTracker, mUsageStats,
-                allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+                allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
+                mAuthenticationStateListeners);
         mScheduler.scheduleClientMonitor(client);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 8ab8892..e44b263 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.face.hidl;
 
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.res.Resources;
@@ -36,6 +38,7 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -65,6 +68,8 @@
 
     private int mLastAcquire;
     private SensorPrivacyManager mSensorPrivacyManager;
+    @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
 
     FaceAuthenticationClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon,
@@ -75,7 +80,8 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
             @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
-            @Authenticators.Types int sensorStrength) {
+            @Authenticators.Types int sensorStrength,
+            @NonNull AuthenticationStateListeners authenticationStateListeners) {
         super(context, lazyDaemon, token, listener, operationId, restricted,
                 options, cookie, requireConfirmation, logger, biometricContext,
                 isStrongBiometric, null /* taskStackListener */,
@@ -84,6 +90,7 @@
         setRequestId(requestId);
         mUsageStats = usageStats;
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+        mAuthenticationStateListeners = authenticationStateListeners;
 
         final Resources resources = getContext().getResources();
         mBiometricPromptIgnoreList = resources.getIntArray(
@@ -186,6 +193,16 @@
                 0 /* error */,
                 0 /* vendorError */,
                 getTargetUserId()));
+
+        if (reportBiometricAuthAttempts()) {
+            if (authenticated) {
+                mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+                        getTargetUserId());
+            } else {
+                mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+                        getTargetUserId());
+            }
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index f7e8123..6912961 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
 import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
 
 import android.annotation.NonNull;
@@ -232,8 +234,16 @@
             if (sidefpsControllerRefactor()) {
                 mAuthenticationStateListeners.onAuthenticationStopped();
             }
+            if (reportBiometricAuthAttempts()) {
+                mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+                        getTargetUserId());
+            }
         } else {
             mState = STATE_STARTED_PAUSED_ATTEMPTED;
+            if (reportBiometricAuthAttempts()) {
+                mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+                        getTargetUserId());
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 4c1d4d6..7a329e9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint.hidl;
 
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
 import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
 
 import android.annotation.NonNull;
@@ -142,6 +144,10 @@
             if (sidefpsControllerRefactor()) {
                 mAuthenticationStateListeners.onAuthenticationStopped();
             }
+            if (reportBiometricAuthAttempts()) {
+                mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+                        getTargetUserId());
+            }
         } else {
             mState = STATE_STARTED_PAUSED_ATTEMPTED;
             final @LockoutTracker.LockoutMode int lockoutMode =
@@ -161,6 +167,10 @@
                 onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
                 cancel();
             }
+            if (reportBiometricAuthAttempts()) {
+                mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+                        getTargetUserId());
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 6ec6a12..77cb08b 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -204,6 +204,10 @@
         }
 
         @Override
+        public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+                String processName) {}
+
+        @Override
         public void onProcessDied(int pid, int uid) {}
 
         @Override
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 403b421..71a9f54 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -248,6 +248,7 @@
             SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
             TelephonyManager telManager = mContext.getSystemService(TelephonyManager.class);
             if (subManager != null && telManager != null) {
+                subManager = subManager.createForAllUserProfiles();
                 List<SubscriptionInfo> subscriptionInfoList =
                         subManager.getActiveSubscriptionInfoList();
                 HashSet<Integer> activeSubIds = new HashSet<Integer>();
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 550aed5..978f468 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -214,6 +214,11 @@
                 }
 
                 @Override
+                public void onProcessStarted(int pid, int processUid, int packageUid,
+                        String packageName, String processName) {
+                }
+
+                @Override
                 public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
                     MediaProjectionManagerService.this.handleForegroundServicesChanged(pid, uid,
                             serviceTypes);
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 85c4ffe..f852b81 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -57,6 +57,7 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
@@ -71,7 +72,6 @@
 import com.android.server.EventLogTags;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
-import com.android.server.notification.Flags;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -81,6 +81,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * NotificationManagerService helper for handling notification attention effects:
@@ -100,6 +101,20 @@
     private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1;
     private static final int DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = 0;
 
+    @VisibleForTesting
+    static final Set<String> NOTIFICATION_AVALANCHE_TRIGGER_INTENTS = Set.of(
+            Intent.ACTION_AIRPLANE_MODE_CHANGED,
+            Intent.ACTION_BOOT_COMPLETED,
+            Intent.ACTION_USER_SWITCHED,
+            Intent.ACTION_MANAGED_PROFILE_AVAILABLE
+    );
+
+    @VisibleForTesting
+    static final Map<String, Pair<String, Boolean>> NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS = Map.of(
+            Intent.ACTION_AIRPLANE_MODE_CHANGED, new Pair<>("state", false),
+            Intent.ACTION_MANAGED_PROFILE_AVAILABLE, new Pair<>(Intent.EXTRA_QUIET_MODE, false)
+    );
+
     private final Context mContext;
     private final PackageManager mPackageManager;
     private final TelephonyManager mTelephonyManager;
@@ -191,7 +206,7 @@
         mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
 
         if (Flags.politeNotifications()) {
-            mStrategy = getPolitenessStrategy();
+            mStrategy = createPolitenessStrategy();
         } else {
             mStrategy = null;
         }
@@ -200,7 +215,7 @@
         loadUserSettings();
     }
 
-    private PolitenessStrategy getPolitenessStrategy() {
+    private PolitenessStrategy createPolitenessStrategy() {
         if (Flags.crossAppPoliteNotifications()) {
             PolitenessStrategy appStrategy = new StrategyPerApp(
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
@@ -209,11 +224,12 @@
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
 
-            return new StrategyGlobal(
+            return new StrategyAvalanche(
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT),
                     appStrategy);
         } else {
             return new StrategyPerApp(
@@ -225,6 +241,11 @@
         }
     }
 
+    @VisibleForTesting
+    PolitenessStrategy getPolitenessStrategy() {
+        return mStrategy;
+    }
+
     public void onSystemReady() {
         mSystemReady = true;
 
@@ -259,6 +280,11 @@
         filter.addAction(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
+        if (Flags.crossAppPoliteNotifications()) {
+            for (String avalancheIntent : NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
+                filter.addAction(avalancheIntent);
+            }
+        }
         mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
 
         mContext.getContentResolver().registerContentObserver(
@@ -1052,7 +1078,8 @@
         }
     }
 
-    abstract private static class PolitenessStrategy {
+    @VisibleForTesting
+    abstract static class PolitenessStrategy {
         static final int POLITE_STATE_DEFAULT = 0;
         static final int POLITE_STATE_POLITE = 1;
         static final int POLITE_STATE_MUTED = 2;
@@ -1079,6 +1106,8 @@
         protected boolean mApplyPerPackage;
         protected final Map<String, Long> mLastUpdatedTimestampByPackage;
 
+        protected boolean mIsActive = true;
+
         public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
                 int volumeMuted) {
             mVolumeStates = new HashMap<>();
@@ -1218,6 +1247,10 @@
             }
             return nextState;
         }
+
+        boolean isActive() {
+            return mIsActive;
+        }
     }
 
     // TODO b/270456865: Only one of the two strategies will be released.
@@ -1289,55 +1322,60 @@
     }
 
     /**
-     * Global (cross-app) strategy.
+     * Avalanche (cross-app) strategy.
      */
-    private static class StrategyGlobal extends PolitenessStrategy {
+    private static class StrategyAvalanche extends PolitenessStrategy {
         private static final String COMMON_KEY = "cross_app_common_key";
 
         private final PolitenessStrategy mAppStrategy;
         private long mLastNotificationTimestamp = 0;
 
-        public StrategyGlobal(int timeoutPolite, int timeoutMuted, int volumePolite,
-                int volumeMuted, PolitenessStrategy appStrategy) {
+        private final int mTimeoutAvalanche;
+        private long mLastAvalancheTriggerTimestamp = 0;
+
+        StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite,
+                    int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy) {
             super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
 
+            mTimeoutAvalanche = timeoutAvalanche;
             mAppStrategy = appStrategy;
 
             if (DEBUG) {
-                Log.i(TAG, "StrategyGlobal: " + timeoutPolite + " " + timeoutMuted);
+                Log.i(TAG, "StrategyAvalanche: " + timeoutPolite + " " + timeoutMuted + " "
+                        + timeoutAvalanche);
             }
         }
 
         @Override
         void onNotificationPosted(NotificationRecord record) {
-            if (shouldIgnoreNotification(record)) {
-                return;
-            }
+            if (isAvalancheActive()) {
+                if (shouldIgnoreNotification(record)) {
+                    return;
+                }
 
-            long timeSinceLastNotif =
+                long timeSinceLastNotif =
                     System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
 
-            final String key = getChannelKey(record);
-            @PolitenessState final int currState = getPolitenessState(record);
-            @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
+                final String key = getChannelKey(record);
+                @PolitenessState final int currState = getPolitenessState(record);
+                @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
 
-            if (DEBUG) {
-                Log.i(TAG, "StrategyGlobal onNotificationPosted time delta: " + timeSinceLastNotif
-                        + " vol state: " + nextState + " key: " + key);
+                if (DEBUG) {
+                    Log.i(TAG,
+                            "StrategyAvalanche onNotificationPosted time delta: "
+                            + timeSinceLastNotif
+                            + " vol state: " + nextState + " key: " + key);
+                }
+
+                mVolumeStates.put(key, nextState);
             }
 
-            mVolumeStates.put(key, nextState);
-
             mAppStrategy.onNotificationPosted(record);
         }
 
         @Override
         public float getSoundVolume(final NotificationRecord record) {
-            final @PolitenessState int globalVolState = getPolitenessState(record);
-            final @PolitenessState int appVolState = mAppStrategy.getPolitenessState(record);
-
-            // Prioritize the most polite outcome
-            if (globalVolState > appVolState) {
+            if (isAvalancheActive()) {
                 return super.getSoundVolume(record);
             } else {
                 return mAppStrategy.getSoundVolume(record);
@@ -1382,6 +1420,24 @@
             super.setApplyCooldownPerPackage(applyPerPackage);
             mAppStrategy.setApplyCooldownPerPackage(applyPerPackage);
         }
+
+        boolean isAvalancheActive() {
+            mIsActive = (System.currentTimeMillis() - mLastAvalancheTriggerTimestamp
+                    < mTimeoutAvalanche);
+            if (DEBUG) {
+                Log.i(TAG, "StrategyAvalanche: active " + mIsActive);
+            }
+            return mIsActive;
+        }
+
+        @Override
+        boolean isActive() {
+            return isAvalancheActive();
+        }
+
+        void setTriggerTimeMs(long timestamp) {
+            mLastAvalancheTriggerTimestamp = timestamp;
+        }
     }
 
     //======================  Observers  =============================
@@ -1415,6 +1471,30 @@
                         || action.equals(Intent.ACTION_USER_UNLOCKED)) {
                 loadUserSettings();
             }
+
+            if (Flags.crossAppPoliteNotifications()) {
+                if (NOTIFICATION_AVALANCHE_TRIGGER_INTENTS.contains(action)) {
+                    boolean enableAvalancheStrategy = true;
+                    // Some actions must also match extras, ie. airplane mode => disabled
+                    Pair<String, Boolean> expectedExtras =
+                            NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS.get(action);
+                    if (expectedExtras != null) {
+                        enableAvalancheStrategy =
+                                intent.getBooleanExtra(expectedExtras.first, false)
+                                == expectedExtras.second;
+                    }
+
+                    if (DEBUG) {
+                        Log.i(TAG, "Avalanche trigger intent received: " + action
+                                + ". Enabling avalanche strategy: " + enableAvalancheStrategy);
+                    }
+
+                    if (enableAvalancheStrategy && mStrategy instanceof StrategyAvalanche) {
+                        ((StrategyAvalanche) mStrategy)
+                                .setTriggerTimeMs(System.currentTimeMillis());
+                    }
+                }
+            }
         }
     };
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index f311034..ada79ae 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -4217,8 +4217,10 @@
             }
         }
 
+        final long firstInstallTime = Flags.fixSystemAppsFirstInstallTime()
+                ? System.currentTimeMillis() : 0;
         final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
-                scanFlags | SCAN_UPDATE_SIGNATURE, 0 /* currentTime */, user, null);
+                scanFlags | SCAN_UPDATE_SIGNATURE, firstInstallTime, user, null);
         return new Pair<>(scanResult, shouldHideSystemApp);
     }
 
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
index c8c16db..f5dfb5c 100644
--- a/services/core/java/com/android/server/power/feature/power_flags.aconfig
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -4,7 +4,7 @@
 
 flag {
     name: "enable_early_screen_timeout_detector"
-    namespace: "power_manager"
+    namespace: "power"
     description: "Feature flag for Early Screen Timeout detector"
     bug: "309861917"
     is_fixed_read_only: true
diff --git a/services/core/java/com/android/server/stats/Android.bp b/services/core/java/com/android/server/stats/Android.bp
new file mode 100644
index 0000000..e597c3a
--- /dev/null
+++ b/services/core/java/com/android/server/stats/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+    name: "stats_flags",
+    package: "com.android.server.stats",
+    srcs: [
+        "stats_flags.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "stats_flags_lib",
+    aconfig_declarations: "stats_flags",
+}
diff --git a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
new file mode 100644
index 0000000..0de73a5
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull;
+
+import android.app.ActivityManager;
+import android.app.StatsManager;
+import android.app.usage.NetworkStatsManager;
+import android.net.NetworkStats;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Trace;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.StatsEvent;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Aggregates Mobile Data Usage by process state per uid
+ */
+class AggregatedMobileDataStatsPuller {
+    private static final String TAG = "AggregatedMobileDataStatsPuller";
+
+    private static final boolean DEBUG = false;
+
+    private static class UidProcState {
+
+        private final int mUid;
+        private final int mState;
+
+        UidProcState(int uid, int state) {
+            mUid = uid;
+            mState = state;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof UidProcState key)) return false;
+            return mUid == key.mUid && mState == key.mState;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = mUid;
+            result = 31 * result + mState;
+            return result;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public int getState() {
+            return mState;
+        }
+
+    }
+
+    private static class MobileDataStats {
+        private long mRxPackets = 0;
+        private long mTxPackets = 0;
+        private long mRxBytes = 0;
+        private long mTxBytes = 0;
+
+        public long getRxPackets() {
+            return mRxPackets;
+        }
+
+        public long getTxPackets() {
+            return mTxPackets;
+        }
+
+        public long getRxBytes() {
+            return mRxBytes;
+        }
+
+        public long getTxBytes() {
+            return mTxBytes;
+        }
+
+        public void addRxPackets(long rxPackets) {
+            mRxPackets += rxPackets;
+        }
+
+        public void addTxPackets(long txPackets) {
+            mTxPackets += txPackets;
+        }
+
+        public void addRxBytes(long rxBytes) {
+            mRxBytes += rxBytes;
+        }
+
+        public void addTxBytes(long txBytes) {
+            mTxBytes += txBytes;
+        }
+
+        public boolean isEmpty() {
+            return mRxPackets == 0 && mTxPackets == 0 && mRxBytes == 0 && mTxBytes == 0;
+        }
+    }
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final Map<UidProcState, MobileDataStats> mUidStats;
+
+    private final SparseIntArray mUidPreviousState;
+
+    private NetworkStats mLastMobileUidStats = new NetworkStats(0, -1);
+
+    private final NetworkStatsManager mNetworkStatsManager;
+
+    private final Handler mMobileDataStatsHandler;
+
+    AggregatedMobileDataStatsPuller(NetworkStatsManager networkStatsManager) {
+        mUidStats = new ArrayMap<>();
+        mUidPreviousState = new SparseIntArray();
+
+        mNetworkStatsManager = networkStatsManager;
+
+        if (mNetworkStatsManager != null) {
+            updateNetworkStats(mNetworkStatsManager);
+        }
+
+        HandlerThread mMobileDataStatsHandlerThread = new HandlerThread("MobileDataStatsHandler");
+        mMobileDataStatsHandlerThread.start();
+        mMobileDataStatsHandler = new Handler(mMobileDataStatsHandlerThread.getLooper());
+    }
+
+    public void noteUidProcessState(int uid, int state, long unusedElapsedRealtime,
+                                    long unusedUptime) {
+        mMobileDataStatsHandler.post(
+                () -> {
+                    noteUidProcessStateImpl(uid, state);
+                });
+    }
+
+    public int pullDataBytesTransfer(List<StatsEvent> data) {
+        synchronized (mLock) {
+            return pullDataBytesTransferLocked(data);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private MobileDataStats getUidStatsForPreviousStateLocked(int uid) {
+        final int previousState = mUidPreviousState.get(uid, ActivityManager.PROCESS_STATE_UNKNOWN);
+        if (DEBUG && previousState == ActivityManager.PROCESS_STATE_UNKNOWN) {
+            Slog.d(TAG, "getUidStatsForPreviousStateLocked() no prev state info for uid "
+                    + uid + ". Tracking stats with ActivityManager.PROCESS_STATE_UNKNOWN");
+        }
+
+        final UidProcState statsKey = new UidProcState(uid, previousState);
+        MobileDataStats stats;
+        if (mUidStats.containsKey(statsKey)) {
+            stats = mUidStats.get(statsKey);
+        } else {
+            stats = new MobileDataStats();
+            mUidStats.put(statsKey, stats);
+        }
+        return stats;
+    }
+
+    private void noteUidProcessStateImpl(int uid, int state) {
+        // noteUidProcessStateLocked can be called back to back several times while
+        // the updateNetworkStatsLocked loops over several stats for multiple uids
+        // and during the first call in a batch of proc state change event it can
+        // contain info for uid with unknown previous state yet which can happen due to a few
+        // reasons:
+        // - app was just started
+        // - app was started before the ActivityManagerService
+        // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN
+        if (mNetworkStatsManager != null) {
+            updateNetworkStats(mNetworkStatsManager);
+        } else {
+            Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager");
+        }
+        mUidPreviousState.put(uid, state);
+    }
+
+    private void updateNetworkStats(NetworkStatsManager networkStatsManager) {
+        if (DEBUG) {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+                Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStats");
+            }
+        }
+
+        final NetworkStats latestStats = networkStatsManager.getMobileUidStats();
+        if (isEmpty(latestStats)) {
+            if (DEBUG) {
+                Slog.w(TAG, "getMobileUidStats() failed");
+                Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+            }
+            return;
+        }
+        NetworkStats delta = latestStats.subtract(mLastMobileUidStats);
+        mLastMobileUidStats = latestStats;
+
+        if (!isEmpty(delta)) {
+            updateNetworkStatsDelta(delta);
+        } else if (DEBUG) {
+            Slog.w(TAG, "updateNetworkStats() no delta");
+        }
+        if (DEBUG) {
+            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+        }
+    }
+
+    private void updateNetworkStatsDelta(NetworkStats delta) {
+        synchronized (mLock) {
+            for (NetworkStats.Entry entry : delta) {
+                if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
+                    continue;
+                }
+                MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid());
+                stats.addTxBytes(entry.getTxBytes());
+                stats.addRxBytes(entry.getRxBytes());
+                stats.addTxPackets(entry.getTxPackets());
+                stats.addRxPackets(entry.getRxPackets());
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private int pullDataBytesTransferLocked(List<StatsEvent> pulledData) {
+        if (DEBUG) {
+            Slog.d(TAG, "pullDataBytesTransferLocked() start");
+        }
+        for (Map.Entry<UidProcState, MobileDataStats> uidStats : mUidStats.entrySet()) {
+            if (!uidStats.getValue().isEmpty()) {
+                MobileDataStats stats = uidStats.getValue();
+                pulledData.add(FrameworkStatsLog.buildStatsEvent(
+                        FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE,
+                        uidStats.getKey().getUid(),
+                        ActivityManager.processStateAmToProto(uidStats.getKey().getState()),
+                        stats.getRxBytes(),
+                        stats.getRxPackets(),
+                        stats.getTxBytes(),
+                        stats.getTxPackets()));
+            }
+        }
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "pullDataBytesTransferLocked() done. results count " + pulledData.size());
+        }
+        if (!pulledData.isEmpty()) {
+            return StatsManager.PULL_SUCCESS;
+        }
+        return StatsManager.PULL_SKIP;
+    }
+
+    private static boolean isEmpty(NetworkStats stats) {
+        long totalRxPackets = 0;
+        long totalTxPackets = 0;
+        for (NetworkStats.Entry entry : stats) {
+            if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
+                continue;
+            }
+            totalRxPackets += entry.getRxPackets();
+            totalTxPackets += entry.getTxPackets();
+            // at least one non empty entry located
+            break;
+        }
+        final long totalPackets = totalRxPackets + totalTxPackets;
+        return totalPackets == 0;
+    }
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index e876241..285bcc3 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -59,6 +59,7 @@
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY;
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
 import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
 import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
 import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
@@ -409,6 +410,15 @@
     @GuardedBy("mKeystoreLock")
     private IKeystoreMetrics mIKeystoreMetrics;
 
+    private AggregatedMobileDataStatsPuller mAggregatedMobileDataStatsPuller = null;
+
+    /**
+     * Whether or not to enable the new puller with aggregation by process state per uid on a
+     * system server side.
+     */
+    public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER =
+                addMobileBytesTransferByProcStatePuller();
+
     // Puller locks
     private final Object mDataBytesTransferLock = new Object();
     private final Object mBluetoothBytesTransferLock = new Object();
@@ -469,6 +479,20 @@
         mContext = context;
     }
 
+    private final class StatsPullAtomServiceInternalImpl extends StatsPullAtomServiceInternal {
+
+        @Override
+        public void noteUidProcessState(int uid, int state) {
+            if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER
+                    && mAggregatedMobileDataStatsPuller != null) {
+                final long elapsedRealtime = SystemClock.elapsedRealtime();
+                final long uptime = SystemClock.uptimeMillis();
+                mAggregatedMobileDataStatsPuller.noteUidProcessState(uid, state, elapsedRealtime,
+                        uptime);
+            }
+        }
+    }
+
     private native void initializeNativePullers();
 
     /**
@@ -486,6 +510,11 @@
             }
             try {
                 switch (atomTag) {
+                    case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE:
+                        if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER
+                                && mAggregatedMobileDataStatsPuller != null) {
+                            return mAggregatedMobileDataStatsPuller.pullDataBytesTransfer(data);
+                        }
                     case FrameworkStatsLog.WIFI_BYTES_TRANSFER:
                     case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG:
                     case FrameworkStatsLog.MOBILE_BYTES_TRANSFER:
@@ -776,7 +805,10 @@
 
     @Override
     public void onStart() {
-        // no op
+        if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+            LocalServices.addService(StatsPullAtomServiceInternal.class,
+                    new StatsPullAtomServiceInternalImpl());
+        }
     }
 
     @Override
@@ -811,6 +843,9 @@
         mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
         mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
         mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
+
+        initMobileDataStatsPuller();
+
         // Initialize DiskIO
         mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
 
@@ -972,6 +1007,18 @@
         registerCachedAppsHighWatermarkPuller();
     }
 
+    private void initMobileDataStatsPuller() {
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER = "
+                            + ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER);
+        }
+        if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+            mAggregatedMobileDataStatsPuller =
+                    new AggregatedMobileDataStatsPuller(mNetworkStatsManager);
+        }
+    }
+
     private void initAndRegisterNetworkStatsPullers() {
         if (DEBUG) {
             Slog.d(TAG, "Registering NetworkStats pullers with statsd");
@@ -1013,6 +1060,9 @@
         registerWifiBytesTransferBackground();
         registerMobileBytesTransfer();
         registerMobileBytesTransferBackground();
+        if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+            registerMobileBytesTransferByProcState();
+        }
         registerBytesTransferByTagAndMetered();
         registerDataUsageBytesTransfer();
         registerOemManagedBytesTransfer();
@@ -1021,6 +1071,13 @@
         }
     }
 
+    private void registerMobileBytesTransferByProcState() {
+        final int tagId = FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE;
+        PullAtomMetadata metadata =
+                new PullAtomMetadata.Builder().setAdditiveFields(new int[] {3, 4, 5, 6}).build();
+        mStatsManager.setPullAtomCallback(tagId, metadata, DIRECT_EXECUTOR, mStatsCallbackImpl);
+    }
+
     private void initAndRegisterDeferredPullers() {
         mUwbManager = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)
             ? mContext.getSystemService(UwbManager.class) : null;
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java
new file mode 100644
index 0000000..06adbfc
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull;
+
+/**
+ * System-server internal interface to the {@link StatsPullAtomService}.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class StatsPullAtomServiceInternal {
+
+    /**
+     * @param state Process state from ActivityManager.java.
+     */
+    public abstract void noteUidProcessState(int uid, int state);
+
+}
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
new file mode 100644
index 0000000..5101a69
--- /dev/null
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.server.stats"
+
+flag {
+    name: "add_mobile_bytes_transfer_by_proc_state_puller"
+    namespace: "statsd"
+    description: "Adds mobile_bytes_transfer_by_proc_state atom with system server side aggregation"
+    bug: "309512867"
+    is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 106be5f..4cc2c02 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -48,6 +48,7 @@
 import java.io.FileDescriptor;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * System service for managing sensing {@link AmbientContextEvent}s on Wearables.
@@ -191,9 +192,23 @@
         }
     }
 
+    private void callPerUserServiceIfExist(
+            Consumer<WearableSensingManagerPerUserService> serviceConsumer,
+            RemoteCallback statusCallback) {
+        int userId = UserHandle.getCallingUserId();
+        synchronized (mLock) {
+            WearableSensingManagerPerUserService service = getServiceForUserLocked(userId);
+            if (service == null) {
+                Slog.w(TAG, "Service not available for userId " + userId);
+                WearableSensingManagerPerUserService.notifyStatusCallback(statusCallback,
+                        WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            serviceConsumer.accept(service);
+        }
+    }
+
     private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
-        final WearableSensingManagerPerUserService mService = getServiceForUserLocked(
-                UserHandle.getCallingUserId());
 
         @Override
         public void provideDataStream(
@@ -210,7 +225,9 @@
                         WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
                 return;
             }
-            mService.onProvideDataStream(parcelFileDescriptor, callback);
+            callPerUserServiceIfExist(
+                    service -> service.onProvideDataStream(parcelFileDescriptor, callback),
+                    callback);
         }
 
         @Override
@@ -229,7 +246,9 @@
                         WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
                 return;
             }
-            mService.onProvidedData(data, sharedMemory, callback);
+            callPerUserServiceIfExist(
+                    service -> service.onProvidedData(data, sharedMemory, callback),
+                    callback);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 036f7b6..3d492bb 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -141,6 +141,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
@@ -991,6 +992,9 @@
     private CustomAppTransition mCustomOpenTransition;
     private CustomAppTransition mCustomCloseTransition;
 
+    /** Non-zero to pause dispatching configuration changes to the client. */
+    int mPauseConfigurationDispatchCount = 0;
+
     private final Runnable mPauseTimeoutRunnable = new Runnable() {
         @Override
         public void run() {
@@ -9276,6 +9280,59 @@
         }
     }
 
+    @Override
+    void dispatchConfigurationToChild(WindowState child, Configuration config) {
+        if (isConfigurationDispatchPaused()) {
+            return;
+        }
+        super.dispatchConfigurationToChild(child, config);
+    }
+
+    /**
+     * Pauses dispatch of configuration changes to the client. This includes any
+     * configuration-triggered lifecycle changes, WindowState configs, and surface changes. If
+     * a lifecycle change comes from another source (eg. stop), it will still run but will use the
+     * paused configuration.
+     *
+     * The main way this works is by blocking calls to {@link #updateReportedConfigurationAndSend}.
+     * That method is responsible for evaluating whether the activity needs to be relaunched and
+     * sending configurations.
+     */
+    void pauseConfigurationDispatch() {
+        ++mPauseConfigurationDispatchCount;
+        if (mPauseConfigurationDispatchCount == 1) {
+            ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Pausing configuration dispatch for "
+                    + " %s", this);
+        }
+    }
+
+    /** @return `true` if configuration actually changed. */
+    boolean resumeConfigurationDispatch() {
+        --mPauseConfigurationDispatchCount;
+        if (mPauseConfigurationDispatchCount > 0) {
+            return false;
+        }
+        ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Resuming configuration dispatch for %s", this);
+        if (mPauseConfigurationDispatchCount < 0) {
+            Slog.wtf(TAG, "Trying to resume non-paused configuration dispatch");
+            mPauseConfigurationDispatchCount = 0;
+            return false;
+        }
+        if (mLastReportedDisplayId == getDisplayId()
+                && getConfiguration().equals(mLastReportedConfiguration.getMergedConfiguration())) {
+            return false;
+        }
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            dispatchConfigurationToChild(getChildAt(i), getConfiguration());
+        }
+        updateReportedConfigurationAndSend();
+        return true;
+    }
+
+    boolean isConfigurationDispatchPaused() {
+        return mPauseConfigurationDispatchCount > 0;
+    }
+
     private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
             Rect containingBounds) {
         return applyAspectRatio(outBounds, containingAppBounds, containingBounds,
@@ -9525,6 +9582,17 @@
             return true;
         }
 
+        if (isConfigurationDispatchPaused()) {
+            return true;
+        }
+
+        return updateReportedConfigurationAndSend();
+    }
+
+    boolean updateReportedConfigurationAndSend() {
+        if (isConfigurationDispatchPaused()) {
+            Slog.wtf(TAG, "trying to update reported(client) config while dispatch is paused");
+        }
         ProtoLog.v(WM_DEBUG_CONFIGURATION, "Ensuring correct "
                 + "configuration: %s", this);
 
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 7f785af..a1799b4 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -92,35 +92,39 @@
         mRotation = rotation;
         mWidth = w;
         mHeight = h;
-        final Rect unrestricted = mUnrestricted;
-        unrestricted.set(0, 0, w, h);
-        state.setDisplayFrame(unrestricted);
+        final Rect u = mUnrestricted;
+        u.set(0, 0, w, h);
+        state.setDisplayFrame(u);
         state.setDisplayCutout(displayCutout);
         state.setRoundedCorners(roundedCorners);
         state.setPrivacyIndicatorBounds(indicatorBounds);
         state.setDisplayShape(displayShape);
         state.getDisplayCutoutSafe(safe);
-        if (safe.left > unrestricted.left) {
-            state.getOrCreateSource(ID_DISPLAY_CUTOUT_LEFT, displayCutout()).setFrame(
-                    unrestricted.left, unrestricted.top, safe.left, unrestricted.bottom);
+        if (safe.left > u.left) {
+            state.getOrCreateSource(ID_DISPLAY_CUTOUT_LEFT, displayCutout())
+                    .setFrame(u.left, u.top, safe.left, u.bottom)
+                    .updateSideHint(u);
         } else {
             state.removeSource(ID_DISPLAY_CUTOUT_LEFT);
         }
-        if (safe.top > unrestricted.top) {
-            state.getOrCreateSource(ID_DISPLAY_CUTOUT_TOP, displayCutout()).setFrame(
-                    unrestricted.left, unrestricted.top, unrestricted.right, safe.top);
+        if (safe.top > u.top) {
+            state.getOrCreateSource(ID_DISPLAY_CUTOUT_TOP, displayCutout())
+                    .setFrame(u.left, u.top, u.right, safe.top)
+                    .updateSideHint(u);
         } else {
             state.removeSource(ID_DISPLAY_CUTOUT_TOP);
         }
-        if (safe.right < unrestricted.right) {
-            state.getOrCreateSource(ID_DISPLAY_CUTOUT_RIGHT, displayCutout()).setFrame(
-                    safe.right, unrestricted.top, unrestricted.right, unrestricted.bottom);
+        if (safe.right < u.right) {
+            state.getOrCreateSource(ID_DISPLAY_CUTOUT_RIGHT, displayCutout())
+                    .setFrame(safe.right, u.top, u.right, u.bottom)
+                    .updateSideHint(u);
         } else {
             state.removeSource(ID_DISPLAY_CUTOUT_RIGHT);
         }
-        if (safe.bottom < unrestricted.bottom) {
-            state.getOrCreateSource(ID_DISPLAY_CUTOUT_BOTTOM, displayCutout()).setFrame(
-                    unrestricted.left, safe.bottom, unrestricted.right, unrestricted.bottom);
+        if (safe.bottom < u.bottom) {
+            state.getOrCreateSource(ID_DISPLAY_CUTOUT_BOTTOM, displayCutout())
+                    .setFrame(u.left, safe.bottom, u.right, u.bottom)
+                    .updateSideHint(u);
         } else {
             state.removeSource(ID_DISPLAY_CUTOUT_BOTTOM);
         }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 9d5ddf3..d9dda4a 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -62,6 +62,8 @@
  */
 class InsetsSourceProvider {
 
+    private static final Rect EMPTY_RECT = new Rect();
+
     protected final DisplayContent mDisplayContent;
     protected final @NonNull InsetsSource mSource;
     protected WindowContainer mWindowContainer;
@@ -286,12 +288,15 @@
 
     private void updateSourceFrameForServerVisibility() {
         // Make sure we set the valid source frame only when server visible is true, because the
-        // frame may not yet determined that server side doesn't think the window is ready to
+        // frame may not yet be determined that server side doesn't think the window is ready to
         // visible. (i.e. No surface, pending insets that were given during layout, etc..)
-        if (mServerVisible) {
-            mSource.setFrame(mSourceFrame);
-        } else {
-            mSource.setFrame(0, 0, 0, 0);
+        final Rect frame = mServerVisible ? mSourceFrame : EMPTY_RECT;
+        if (mSource.getFrame().equals(frame)) {
+            return;
+        }
+        mSource.setFrame(frame);
+        if (mWindowContainer != null) {
+            mSource.updateSideHint(mWindowContainer.getBounds());
         }
     }
 
@@ -631,7 +636,7 @@
         }
         pw.print(prefix);
         pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
-        pw.print("mHasPendingPosition="); pw.print(mHasPendingPosition);
+        pw.print(" mHasPendingPosition="); pw.print(mHasPendingPosition);
         pw.println();
         if (mWindowContainer != null) {
             pw.print(prefix + "mWindowContainer=");
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 6371bb4..0c6b174 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -369,6 +369,15 @@
      */
     private boolean mMoveToBottomIfClearWhenLaunch;
 
+    /**
+     * If {@code true}, transitions are allowed even if this TaskFragment is empty. If
+     * {@code false}, transitions will wait until this TaskFragment becomes non-empty or other
+     * conditions are met. Default to {@code false}.
+     *
+     * @see #isReadyToTransit
+     */
+    private boolean mAllowTransitionWhenEmpty;
+
     /** When set, will force the task to report as invisible. */
     static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
     static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
@@ -509,6 +518,19 @@
         mIsolatedNav = isolatedNav;
     }
 
+    /**
+     * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true},
+     * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions
+     * will wait until the TaskFragment becomes non-empty or other conditions are met. Default
+     * to {@code false}.
+     */
+    void setAllowTransitionWhenEmpty(boolean allowTransitionWhenEmpty) {
+        if (!isEmbedded()) {
+            return;
+        }
+        mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
+    }
+
     /** @see #mIsolatedNav */
     boolean isIsolatedNav() {
         return isEmbedded() && mIsolatedNav;
@@ -2827,8 +2849,9 @@
             return true;
         }
         // We don't want to start the transition if the organized TaskFragment is empty, unless
-        // it is requested to be removed.
-        if (getTopNonFinishingActivity() != null || mIsRemovalRequested) {
+        // it is requested to be removed or the mAllowTransitionWhenEmpty flag is true.
+        if (getTopNonFinishingActivity() != null || mIsRemovalRequested
+                || mAllowTransitionWhenEmpty) {
             return true;
         }
         // Organizer shouldn't change embedded TaskFragment in PiP.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index bdea1bc..286182e 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -465,7 +465,7 @@
             }
         }
         final InsetsSource source = new InsetsSource(id, provider.getType());
-        source.setFrame(provider.getArbitraryRectangle());
+        source.setFrame(provider.getArbitraryRectangle()).updateSideHint(getBounds());
         mLocalInsetsSources.put(id, source);
         mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
     }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 205ed97..4ba52e4 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -2202,6 +2202,7 @@
         }
         final TaskFragment taskFragment = new TaskFragment(mService,
                 creationParams.getFragmentToken(), true /* createdByOrganizer */);
+        taskFragment.setAllowTransitionWhenEmpty(creationParams.getAllowTransitionWhenEmpty());
         // Set task fragment organizer immediately, since it might have to be notified about further
         // actions.
         TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 56f2bc3..7ad87ed 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5187,6 +5187,11 @@
         if (mSurfaceControl == null) {
             return;
         }
+        if (mActivityRecord != null && mActivityRecord.isConfigurationDispatchPaused()) {
+            // Don't update surface-position while dispatch paused. This is calculated from
+            // the server-side activity configuration so return early.
+            return;
+        }
 
         if ((mWmService.mWindowPlacerLocked.isLayoutDeferred() || isGoneForLayout())
                 && !mSurfacePlacementNeeded) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5048cef..13e1ba78 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -639,9 +639,12 @@
 
     @Override
     void updateSurfacePosition(SurfaceControl.Transaction t) {
+        final ActivityRecord r = asActivityRecord();
+        if (r != null && r.isConfigurationDispatchPaused()) {
+            return;
+        }
         super.updateSurfacePosition(t);
         if (!mTransitionController.isShellTransitionsEnabled() && isFixedRotationTransforming()) {
-            final ActivityRecord r = asActivityRecord();
             final Task rootTask = r != null ? r.getRootTask() : null;
             // Don't transform the activity in PiP because the PiP task organizer will handle it.
             if (rootTask == null || !rootTask.inPinnedWindowingMode()) {
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index a0fb013..24d4952 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -94,7 +94,9 @@
         isSystemUpdated: Boolean
     ) {
         packageNames.forEachIndexed { _, packageName ->
-            val packageState = newState.externalState.packageStates[packageName]!!
+            // The package may still be removed even if it was once notified as installed.
+            val packageState = newState.externalState.packageStates[packageName]
+                ?: return@forEachIndexed
             trimPermissionStates(packageState.appId)
         }
     }
@@ -245,6 +247,13 @@
         flagMask: Int,
         flagValues: Int
     ): Boolean {
+        if (userId !in newState.userStates) {
+            // Despite that we check UserManagerInternal.exists() in PermissionService, we may still
+            // sometimes get race conditions between that check and the actual mutateState() call.
+            // This should rarely happen but at least we should not crash.
+            Slog.e(LOG_TAG, "Unable to update permission flags for missing user $userId")
+            return false
+        }
         val oldFlags =
             newState.userStates[userId]!!
                 .appIdDevicePermissionFlags[appId]
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
new file mode 100644
index 0000000..fcf761f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.os.Process.myPid;
+import static android.os.Process.myUid;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManagerInternal;
+import android.app.IApplicationThread;
+import android.app.IProcessObserver;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.am.ActivityManagerService.Injector;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.Arrays;
+
+
+/**
+ * Tests to verify that process events are dispatched to process observers.
+ */
+@MediumTest
+@SuppressWarnings("GuardedBy")
+public class ProcessObserverTest {
+    private static final String TAG = "ProcessObserverTest";
+
+    private static final String PACKAGE = "com.foo";
+
+    @Rule
+    public final ApplicationExitInfoTest.ServiceThreadRule
+            mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
+
+    private Context mContext;
+    private HandlerThread mHandlerThread;
+
+    @Mock
+    private AppOpsService mAppOpsService;
+    @Mock
+    private DropBoxManagerInternal mDropBoxManagerInt;
+    @Mock
+    private PackageManagerInternal mPackageManagerInt;
+    @Mock
+    private UsageStatsManagerInternal mUsageStatsManagerInt;
+    @Mock
+    private ActivityManagerInternal mActivityManagerInt;
+    @Mock
+    private ActivityTaskManagerInternal mActivityTaskManagerInt;
+    @Mock
+    private BatteryStatsService mBatteryStatsService;
+
+    private ActivityManagerService mRealAms;
+    private ActivityManagerService mAms;
+
+    private ProcessList mRealProcessList = new ProcessList();
+    private ProcessList mProcessList;
+
+    final IProcessObserver mProcessObserver = mock(IProcessObserver.Stub.class);
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+
+        LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+        LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt);
+
+        LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+        LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt);
+
+        doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+        doReturn(true).when(mActivityTaskManagerInt).attachApplication(any());
+        doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any());
+
+        mRealAms = new ActivityManagerService(
+                new TestInjector(mContext), mServiceThreadRule.getThread());
+        mRealAms.mConstants.loadDeviceConfigConstants();
+        mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+        mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+        mRealAms.mAtmInternal = mActivityTaskManagerInt;
+        mRealAms.mPackageManagerInt = mPackageManagerInt;
+        mRealAms.mUsageStatsService = mUsageStatsManagerInt;
+        mRealAms.mProcessesReady = true;
+        mAms = spy(mRealAms);
+        mRealProcessList.mService = mAms;
+        mProcessList = spy(mRealProcessList);
+
+        doReturn(mProcessObserver).when(mProcessObserver).asBinder();
+        mProcessList.registerProcessObserver(mProcessObserver);
+
+        doAnswer((invocation) -> {
+            Log.v(TAG, "Intercepting isProcStartValidLocked() for "
+                    + Arrays.toString(invocation.getArguments()));
+            return null;
+        }).when(mProcessList).isProcStartValidLocked(any(), anyLong());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+    }
+
+    private class TestInjector extends Injector {
+        TestInjector(Context context) {
+            super(context);
+        }
+
+        @Override
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
+            return mAppOpsService;
+        }
+
+        @Override
+        public Handler getUiHandler(ActivityManagerService service) {
+            return mHandlerThread.getThreadHandler();
+        }
+
+        @Override
+        public ProcessList getProcessList(ActivityManagerService service) {
+            return mRealProcessList;
+        }
+
+        @Override
+        public BatteryStatsService getBatteryStatsService() {
+            return mBatteryStatsService;
+        }
+    }
+
+    private ProcessRecord makeActiveProcessRecord(String packageName)
+            throws Exception {
+        final ApplicationInfo ai = makeApplicationInfo(packageName);
+        return makeActiveProcessRecord(ai);
+    }
+
+    private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai)
+            throws Exception {
+        final IApplicationThread thread = mock(IApplicationThread.class);
+        final IBinder threadBinder = new Binder();
+        doReturn(threadBinder).when(thread).asBinder();
+        doAnswer((invocation) -> {
+            Log.v(TAG, "Intercepting bindApplication() for "
+                    + Arrays.toString(invocation.getArguments()));
+            if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) {
+                mRealAms.finishAttachApplication(0);
+            }
+            return null;
+        }).when(thread).bindApplication(
+                any(), any(),
+                any(), any(), anyBoolean(),
+                any(), any(),
+                any(), any(),
+                any(),
+                any(), anyInt(),
+                anyBoolean(), anyBoolean(),
+                anyBoolean(), anyBoolean(), any(),
+                any(), any(), any(),
+                any(), any(),
+                any(), any(),
+                any(),
+                anyLong(), anyLong());
+        final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
+        r.setPid(myPid());
+        r.setStartUid(myUid());
+        r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST));
+        r.makeActive(thread, mAms.mProcessStats);
+        doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(),
+                anyBoolean());
+        return r;
+    }
+
+    static ApplicationInfo makeApplicationInfo(String packageName) {
+        final ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = packageName;
+        ai.processName = packageName;
+        ai.uid = myUid();
+        return ai;
+    }
+
+    /**
+     * Verify that a process start event is dispatched to process observers.
+     */
+    @Test
+    public void testNormal() throws Exception {
+        ProcessRecord app = startProcess();
+        verify(mProcessObserver).onProcessStarted(
+                app.getPid(), app.uid, app.info.uid, PACKAGE, PACKAGE);
+    }
+
+    private ProcessRecord startProcess() throws Exception {
+        final ProcessRecord app = makeActiveProcessRecord(PACKAGE);
+        final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE);
+        mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false,
+                /* expectedStartSeq */ 0, /* procAttached */ false);
+        app.getThread().bindApplication(PACKAGE, appInfo,
+                null, null, false,
+                null,
+                null,
+                null, null,
+                null,
+                null, 0,
+                false, false,
+                true, false,
+                null,
+                null, null,
+                null,
+                null, null, null,
+                null, null,
+                0, 0);
+        return app;
+    }
+
+    // TODO: [b/302724778] Remove manual JNI load
+    static {
+        System.loadLibrary("mockingservicestestjni");
+    }
+}
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index 654d7a8d..f49f638 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -44,6 +44,7 @@
         "servicestests-utils",
         "platform-test-annotations",
         "flag-junit",
+        "ravenwood-junit",
     ],
 
     libs: [
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index ca162e0..ba2b538 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -32,6 +32,7 @@
 import android.os.HandlerThread;
 import android.os.UidBatteryConsumer;
 import android.os.UserBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
@@ -57,7 +58,8 @@
 
     private final PowerProfile mPowerProfile;
     private final MockClock mMockClock = new MockClock();
-    private final MockBatteryStatsImpl mBatteryStats;
+    private final File mHistoryDir;
+    private MockBatteryStatsImpl mBatteryStats;
     private Handler mHandler;
 
     private BatteryUsageStats mBatteryUsageStats;
@@ -66,6 +68,10 @@
     private SparseArray<int[]> mCpusByPolicy = new SparseArray<>();
     private SparseArray<int[]> mFreqsByPolicy = new SparseArray<>();
 
+    private int mDisplayCount = -1;
+    private int mPerUidModemModel = -1;
+    private NetworkStats mNetworkStats;
+
     public BatteryUsageStatsRule() {
         this(0, null);
     }
@@ -78,16 +84,38 @@
         mHandler = mock(Handler.class);
         mPowerProfile = spy(new PowerProfile());
         mMockClock.currentTime = currentTime;
-        mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir, mHandler);
-        mBatteryStats.setPowerProfile(mPowerProfile);
+        mHistoryDir = historyDir;
+
+        if (!RavenwoodRule.isUnderRavenwood()) {
+            lateInitBatteryStats();
+        }
 
         mCpusByPolicy.put(0, new int[]{0, 1, 2, 3});
         mCpusByPolicy.put(4, new int[]{4, 5, 6, 7});
         mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000});
         mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000});
+    }
+
+    private void lateInitBatteryStats() {
+        if (mBatteryStats != null) return;
+
+        mBatteryStats = new MockBatteryStatsImpl(mMockClock, mHistoryDir, mHandler);
+        mBatteryStats.setPowerProfile(mPowerProfile);
         mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
 
         mBatteryStats.onSystemReady();
+
+        if (mDisplayCount != -1) {
+            mBatteryStats.setDisplayCountLocked(mDisplayCount);
+        }
+        if (mPerUidModemModel != -1) {
+            synchronized (mBatteryStats) {
+                mBatteryStats.setPerUidModemModel(mPerUidModemModel);
+            }
+        }
+        if (mNetworkStats != null) {
+            mBatteryStats.setNetworkStats(mNetworkStats);
+        }
     }
 
     public MockClock getMockClock() {
@@ -112,7 +140,10 @@
         }
         mCpusByPolicy.put(policy, relatedCpus);
         mFreqsByPolicy.put(policy, frequencies);
-        mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+        if (mBatteryStats != null) {
+            mBatteryStats.setCpuScalingPolicies(
+                    new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+        }
         return this;
     }
 
@@ -174,13 +205,19 @@
 
     public BatteryUsageStatsRule setNumDisplays(int value) {
         when(mPowerProfile.getNumDisplays()).thenReturn(value);
-        mBatteryStats.setDisplayCountLocked(value);
+        mDisplayCount = value;
+        if (mBatteryStats != null) {
+            mBatteryStats.setDisplayCountLocked(mDisplayCount);
+        }
         return this;
     }
 
     public BatteryUsageStatsRule setPerUidModemModel(int perUidModemModel) {
-        synchronized (mBatteryStats) {
-            mBatteryStats.setPerUidModemModel(perUidModemModel);
+        mPerUidModemModel = perUidModemModel;
+        if (mBatteryStats != null) {
+            synchronized (mBatteryStats) {
+                mBatteryStats.setPerUidModemModel(mPerUidModemModel);
+            }
         }
         return this;
     }
@@ -210,7 +247,10 @@
     }
 
     public void setNetworkStats(NetworkStats networkStats) {
-        mBatteryStats.setNetworkStats(networkStats);
+        mNetworkStats = networkStats;
+        if (mBatteryStats != null) {
+            mBatteryStats.setNetworkStats(mNetworkStats);
+        }
     }
 
     @Override
@@ -225,6 +265,7 @@
     }
 
     private void before() {
+        lateInitBatteryStats();
         HandlerThread bgThread = new HandlerThread("bg thread");
         bgThread.start();
         mHandler = new Handler(bgThread.getLooper());
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index be68e9c..8958fac 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -31,6 +31,10 @@
 
         "test-apps/SuspendTestApp/src/**/*.java",
     ],
+
+    kotlincflags: [
+        "-Werror",
+    ],
     static_libs: [
         "frameworks-base-testutils",
         "services.accessibility",
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 88b2ed4..071db68 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics;
 
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.TEST_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -491,6 +492,22 @@
     }
 
     @Test
+    public void testRegisterAuthenticationStateListener_callsFaceService() throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+        setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+
+        mAuthService = new AuthService(mContext, mInjector);
+        mAuthService.onStart();
+
+        final AuthenticationStateListener listener = mock(AuthenticationStateListener.class);
+
+        mAuthService.mImpl.registerAuthenticationStateListener(listener);
+
+        waitForIdle();
+        verify(mFaceService).registerAuthenticationStateListener(eq(listener));
+    }
+
+    @Test
     public void testRegisterKeyguardCallback_callsBiometricServiceRegisterKeyguardCallback()
             throws Exception {
         setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 3a3dd6e..f8b5b04 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
@@ -49,6 +50,7 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.TestableContext;
 
 import androidx.test.filters.SmallTest;
@@ -58,6 +60,7 @@
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -81,6 +84,8 @@
 @SmallTest
 public class FaceAuthenticationClientTest {
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final int USER_ID = 12;
     private static final long OP_ID = 32;
     private static final int WAKE_REASON = WakeReason.LIFT;
@@ -105,6 +110,8 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
+    private AuthenticationStateListeners mAuthenticationStateListeners;
+    @Mock
     private AidlResponseHandler mAidlResponseHandler;
     @Mock
     private ActivityTaskManager mActivityTaskManager;
@@ -264,6 +271,29 @@
         verify(mHal, never()).authenticate(anyInt());
     }
 
+    @Test
+    public void testAuthenticationStateListeners_onAuthenticationSucceeded()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+        final FaceAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
+                true /* authenticated */, new ArrayList<>());
+
+        verify(mAuthenticationStateListeners).onAuthenticationSucceeded(anyInt(), anyInt());
+    }
+
+    @Test
+    public void testAuthenticationStateListeners_onAuthenticationFailed() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+        final FaceAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
+                false /* authenticated */, new ArrayList<>());
+
+        verify(mAuthenticationStateListeners).onAuthenticationFailed(anyInt(), anyInt());
+    }
+
     private FaceAuthenticationClient createClient() throws RemoteException {
         return createClient(2 /* version */, mClientMonitorCallbackConverter,
                 false /* allowBackgroundAuthentication */,
@@ -311,7 +341,8 @@
                 false /* requireConfirmation */,
                 mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
                 mUsageStats, lockoutTracker, allowBackgroundAuthentication,
-                null /* sensorPrivacyManager */, 0 /* biometricStrength */) {
+                null /* sensorPrivacyManager */, 0 /* biometricStrength */,
+                mAuthenticationStateListeners) {
             @Override
             protected ActivityTaskManager getActivityTaskManager() {
                 return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 772ec8b..7648bd17 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -51,6 +51,7 @@
 import com.android.internal.R;
 import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -89,6 +90,8 @@
     private BiometricContext mBiometricContext;
     @Mock
     private BiometricStateCallback mBiometricStateCallback;
+    @Mock
+    private AuthenticationStateListeners mAuthenticationStateListeners;
 
     private final TestLooper mLooper = new TestLooper();
     private SensorProps[] mSensorProps;
@@ -119,8 +122,8 @@
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
         mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
-                mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext,
-                mDaemon, new Handler(mLooper.getLooper()),
+                mAuthenticationStateListeners, mSensorProps, TAG, mLockoutResetDispatcher,
+                mBiometricContext, mDaemon, new Handler(mLooper.getLooper()),
                 false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
     }
 
@@ -154,7 +157,7 @@
         final HidlFaceSensorConfig[] hidlFaceSensorConfig =
                 new HidlFaceSensorConfig[]{faceSensorConfig};
         mFaceProvider = new FaceProvider(mContext,
-                mBiometricStateCallback, hidlFaceSensorConfig, TAG,
+                mBiometricStateCallback, mAuthenticationStateListeners, hidlFaceSensorConfig, TAG,
                 mLockoutResetDispatcher, mBiometricContext, mDaemon,
                 new Handler(mLooper.getLooper()),
                 true /* resetLockoutRequiresChallenge */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index e558c4d..78c1e08 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -44,6 +44,7 @@
 
 import com.android.internal.R;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -81,6 +82,8 @@
     private BiometricContext mBiometricContext;
     @Mock
     private BiometricStateCallback mBiometricStateCallback;
+    @Mock
+    private AuthenticationStateListeners mAuthenticationStateListeners;
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -116,8 +119,8 @@
 
         Face10.sSystemClock = Clock.fixed(
                 Instant.ofEpochMilli(100), ZoneId.of("America/Los_Angeles"));
-        mFace10 = new Face10(mContext, mBiometricStateCallback, sensorProps,
-                mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
+        mFace10 = new Face10(mContext, mBiometricStateCallback, mAuthenticationStateListeners,
+                sensorProps, mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
         mBinder = new Binder();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 774ea5b..4ed6f74 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
 
 import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR;
@@ -451,6 +452,29 @@
     }
 
     @Test
+    public void testAuthenticationStateListeners_onAuthenticationSucceeded()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+        final FingerprintAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+                2 /* deviceId */), true /* authenticated */, new ArrayList<>());
+
+        verify(mAuthenticationStateListeners).onAuthenticationSucceeded(anyInt(), anyInt());
+    }
+
+    @Test
+    public void testAuthenticationStateListeners_onAuthenticationFailed() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+        final FingerprintAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+                2 /* deviceId */), false /* authenticated */, new ArrayList<>());
+
+        verify(mAuthenticationStateListeners).onAuthenticationFailed(anyInt(), anyInt());
+    }
+
+    @Test
     public void cancelsAuthWhenNotInForeground() throws Exception {
         final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo();
         topTask.topActivity = new ComponentName("other", "thing");
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
index 10f27ca..72fa949 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
@@ -80,7 +80,7 @@
         @BeforeClass
         @JvmStatic
         fun checkAllCasesUniquelyNamed() {
-            val duplicateCaseNames = CASES.mapIndexed { caseIndex, testCase ->
+            val duplicateCaseNames = CASES.mapIndexed { _, testCase ->
                 testCase.failures.map {
                     makeTestName(testCase, it.first, Params.Type.FAILURE)
                 } + testCase.allowed.map {
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
index 150822b..c07c4d7 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
@@ -18,12 +18,13 @@
 
 import android.content.Context
 import android.util.Xml
-import androidx.test.InstrumentationRegistry
+import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.SystemConfig
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertThrows
 import org.junit.Rule
 import org.junit.Test
-import org.junit.rules.ExpectedException
 import org.junit.rules.TemporaryFolder
 
 class SystemConfigNamedActorTest {
@@ -37,14 +38,11 @@
         private const val PACKAGE_TWO = "com.test.actor.two"
     }
 
-    private val context: Context = InstrumentationRegistry.getContext()
+    private val context: Context = InstrumentationRegistry.getInstrumentation().context
 
     @get:Rule
     val tempFolder = TemporaryFolder(context.filesDir)
 
-    @get:Rule
-    val expected = ExpectedException.none()
-
     private var uniqueCounter = 0
 
     @Test
@@ -193,11 +191,9 @@
             </config>
         """.write()
 
-        expected.expect(IllegalStateException::class.java)
-        expected.expectMessage("Defining $ACTOR_ONE as $PACKAGE_ONE " +
+        val exc = assertThrows(IllegalStateException::class.java) { assertPermissions() }
+        assertEquals(exc.message, "Defining $ACTOR_ONE as $PACKAGE_ONE " +
                 "for the android namespace is not allowed")
-
-        assertPermissions()
     }
 
     @Test
@@ -217,11 +213,9 @@
             </config>
         """.write()
 
-        expected.expect(IllegalStateException::class.java)
-        expected.expectMessage("Duplicate actor definition for $NAMESPACE_TEST/$ACTOR_ONE;" +
+        val exc = assertThrows(IllegalStateException::class.java) { assertPermissions() }
+        assertEquals(exc.message, "Duplicate actor definition for $NAMESPACE_TEST/$ACTOR_ONE;" +
                 " defined as both $PACKAGE_ONE and $PACKAGE_TWO")
-
-        assertPermissions()
     }
 
     private fun String.write() = tempFolder.root.resolve("${uniqueCounter++}.xml")
@@ -230,5 +224,5 @@
     private fun assertPermissions() = SystemConfig(false).apply {
         val parser = Xml.newPullParser()
         readPermissions(parser, tempFolder.root, 0)
-    }. let { assertThat(it.namedActors) }
+    }.let { assertThat(it.namedActors) }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index bfd2df2d..e75afcc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -27,12 +27,13 @@
 import static android.media.AudioAttributes.USAGE_NOTIFICATION;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
 
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.any;
@@ -43,6 +44,7 @@
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -59,7 +61,10 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.graphics.Color;
@@ -80,6 +85,7 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
@@ -100,6 +106,7 @@
 import java.util.List;
 import java.util.Objects;
 
+import java.util.Set;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -132,6 +139,8 @@
     KeyguardManager mKeyguardManager;
     @Mock
     private UserManager mUserManager;
+    @Mock
+    private PackageManager mPackageManager;
     NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
     private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
         1 << 30);
@@ -171,11 +180,14 @@
     private static final int CUSTOM_LIGHT_OFF = 10000;
     private static final int MAX_VIBRATION_DELAY = 1000;
     private static final float DEFAULT_VOLUME = 1.0f;
+    private BroadcastReceiver mAvalancheBroadcastReceiver;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         getContext().addMockSystemService(Vibrator.class, mVibrator);
+        getContext().addMockSystemService(PackageManager.class, mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
 
         when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
         when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
@@ -214,8 +226,9 @@
 
     private void initAttentionHelper(TestableFlagResolver flagResolver) {
         mAttentionHelper = new NotificationAttentionHelper(getContext(), mock(LightsManager.class),
-            mAccessibilityManager, getContext().getPackageManager(), mUserManager, mUsageStats,
-            mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver);
+                mAccessibilityManager, mPackageManager, mUserManager, mUsageStats,
+                mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver);
+        mAttentionHelper.onSystemReady();
         mAttentionHelper.setVibratorHelper(spy(new VibratorHelper(getContext())));
         mAttentionHelper.setAudioManager(mAudioManager);
         mAttentionHelper.setSystemReady(true);
@@ -226,6 +239,29 @@
         mAttentionHelper.setScreenOn(false);
         mAttentionHelper.setInCallStateOffHook(false);
         mAttentionHelper.mNotificationPulseEnabled = true;
+
+        if (Flags.crossAppPoliteNotifications()) {
+            // Capture BroadcastReceiver for avalanche triggers
+            ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                    ArgumentCaptor.forClass(BroadcastReceiver.class);
+            ArgumentCaptor<IntentFilter> intentFilterCaptor =
+                    ArgumentCaptor.forClass(IntentFilter.class);
+            verify(getContext(), atLeastOnce()).registerReceiverAsUser(
+                    broadcastReceiverCaptor.capture(),
+                    any(), intentFilterCaptor.capture(), any(), any());
+            List<BroadcastReceiver> broadcastReceivers = broadcastReceiverCaptor.getAllValues();
+            List<IntentFilter> intentFilters = intentFilterCaptor.getAllValues();
+
+            assertThat(broadcastReceivers.size()).isAtLeast(1);
+            assertThat(intentFilters.size()).isAtLeast(1);
+            for (int i = 0; i < intentFilters.size(); i++) {
+                final IntentFilter filter = intentFilters.get(i);
+                if (filter.hasAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
+                    mAvalancheBroadcastReceiver = broadcastReceivers.get(i);
+                }
+            }
+            assertThat(mAvalancheBroadcastReceiver).isNotNull();
+        }
     }
 
     //
@@ -2040,7 +2076,7 @@
     }
 
     @Test
-    public void testBeepVolume_politeNotif_GlobalStrategy() throws Exception {
+    public void testBeepVolume_politeNotif_AvalancheStrategy() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
         TestableFlagResolver flagResolver = new TestableFlagResolver();
@@ -2048,6 +2084,11 @@
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
         initAttentionHelper(flagResolver);
 
+        // Trigger avalanche trigger intent
+        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra("state", false);
+        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
         NotificationRecord r = getBeepyNotification();
 
         // set up internal state
@@ -2078,7 +2119,8 @@
     }
 
     @Test
-    public void testBeepVolume_politeNotif_GlobalStrategy_ChannelHasUserSound() throws Exception {
+    public void testBeepVolume_politeNotif_AvalancheStrategy_ChannelHasUserSound()
+            throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
         TestableFlagResolver flagResolver = new TestableFlagResolver();
@@ -2086,6 +2128,11 @@
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
         initAttentionHelper(flagResolver);
 
+        // Trigger avalanche trigger intent
+        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra("state", false);
+        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
         NotificationRecord r = getBeepyNotification();
 
         // set up internal state
@@ -2364,6 +2411,82 @@
         assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
+    @Test
+    public void testAvalancheStrategyTriggers() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        final int avalancheTimeoutMs = 100;
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT, avalancheTimeoutMs);
+        initAttentionHelper(flagResolver);
+
+        // Trigger avalanche trigger intents
+        for (String intentAction
+                : NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
+            // Set the action and extras to trigger the avalanche strategy
+            Intent intent = new Intent(intentAction);
+            Pair<String, Boolean> extras =
+                    NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS
+                        .get(intentAction);
+            if (extras != null) {
+                intent.putExtra(extras.first, extras.second);
+            }
+            mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+            assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isTrue();
+
+            // Wait for avalanche timeout
+            Thread.sleep(avalancheTimeoutMs + 1);
+
+            // Check that avalanche strategy is inactive
+            assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse();
+        }
+    }
+
+    @Test
+    public void testAvalancheStrategyTriggers_disabledExtras() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        initAttentionHelper(flagResolver);
+
+        for (String intentAction
+                : NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
+            Intent intent = new Intent(intentAction);
+            Pair<String, Boolean> extras =
+                    NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS
+                        .get(intentAction);
+            // Test only for intents with extras
+            if (extras != null) {
+                // Set the action extras to NOT trigger the avalanche strategy
+                intent.putExtra(extras.first, !extras.second);
+                mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+                // Check that avalanche strategy is inactive
+                assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse();
+            }
+        }
+    }
+
+    @Test
+    public void testAvalancheStrategyTriggers_nonAvalancheIntents() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        initAttentionHelper(flagResolver);
+
+        // Broadcast intents that are not avalanche triggers
+        final Set<String> notAvalancheTriggerIntents = Set.of(
+                Intent.ACTION_USER_ADDED,
+                Intent.ACTION_SCREEN_ON,
+                Intent.ACTION_POWER_CONNECTED
+        );
+        for (String intentAction : notAvalancheTriggerIntents) {
+            Intent intent = new Intent(intentAction);
+            mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+            // Check that avalanche strategy is inactive
+            assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse();
+        }
+    }
+
     static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
         private final int mRepeatIndex;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 2a89b02..31d6fa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3722,6 +3722,68 @@
         assertFalse(ar.moveFocusableActivityToTop("test"));
     }
 
+    @Test
+    public void testPauseConfigDispatch() throws RemoteException {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setDisplay(mDisplayContent).setCreateActivity(true).build();
+        final ActivityRecord activity = task.getTopNonFinishingActivity();
+        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
+                TYPE_BASE_APPLICATION);
+        attrs.setTitle("AppWindow");
+        final TestWindowState appWindow = createWindowState(attrs, activity);
+        activity.addWindow(appWindow);
+
+        clearInvocations(mClientLifecycleManager);
+        clearInvocations(activity);
+
+        Configuration ro = activity.getRequestedOverrideConfiguration();
+        ro.windowConfiguration.setBounds(new Rect(20, 0, 120, 200));
+        activity.onRequestedOverrideConfigurationChanged(ro);
+        activity.ensureActivityConfiguration();
+        mWm.mRoot.performSurfacePlacement();
+
+        // policy will center the bounds, so just check for matching size here.
+        assertEquals(100, activity.getWindowConfiguration().getBounds().width());
+        assertEquals(100, appWindow.getWindowConfiguration().getBounds().width());
+        // No scheduled transactions since it asked for a restart.
+        verify(mClientLifecycleManager, times(1)).scheduleTransaction(any());
+        verify(activity, times(1)).setLastReportedConfiguration(any(), any());
+        assertTrue(appWindow.mResizeReported);
+
+        // act like everything drew and went idle
+        appWindow.mResizeReported = false;
+        makeLastConfigReportedToClient(appWindow, true);
+
+        // Now pause dispatch and try to resize
+        activity.pauseConfigurationDispatch();
+
+        ro.windowConfiguration.setBounds(new Rect(20, 0, 150, 200));
+        activity.onRequestedOverrideConfigurationChanged(ro);
+        activity.ensureActivityConfiguration();
+        mWm.mRoot.performSurfacePlacement();
+
+        // Activity should get new config (core-side)
+        assertEquals(130, activity.getWindowConfiguration().getBounds().width());
+        // But windows should not get new config.
+        assertEquals(100, appWindow.getWindowConfiguration().getBounds().width());
+        // The client shouldn't receive any changes
+        verify(mClientLifecycleManager, times(1)).scheduleTransaction(any());
+        // and lastReported shouldn't be set.
+        verify(activity, times(1)).setLastReportedConfiguration(any(), any());
+        // There should be no resize reported to client.
+        assertFalse(appWindow.mResizeReported);
+
+        // Now resume dispatch
+        activity.resumeConfigurationDispatch();
+        mWm.mRoot.performSurfacePlacement();
+
+        // Windows and client should now receive updates
+        verify(activity, times(2)).setLastReportedConfiguration(any(), any());
+        verify(mClientLifecycleManager, times(2)).scheduleTransaction(any());
+        assertEquals(130, appWindow.getWindowConfiguration().getBounds().width());
+        assertTrue(appWindow.mResizeReported);
+    }
+
     private ICompatCameraControlCallback getCompatCameraControlCallback() {
         return new ICompatCameraControlCallback.Stub() {
             @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 6759eef..752dc5e8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4794,6 +4794,7 @@
                 new WindowManager.LayoutParams(TYPE_STATUS_BAR);
         final Binder owner = new Binder();
         attrs.gravity = android.view.Gravity.TOP;
+        attrs.height = STATUS_BAR_HEIGHT;
         attrs.layoutInDisplayCutoutMode =
                 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         attrs.setFitInsetsTypes(0 /* types */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 7c7e562..245b2c5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -444,6 +444,10 @@
         // Not ready if the task is still visible when the TaskFragment becomes empty.
         doReturn(true).when(task).isVisibleRequested();
         assertFalse(taskFragment.isReadyToTransit());
+
+        // Ready if the mAllowTransitionWhenEmpty flag is true.
+        taskFragment.setAllowTransitionWhenEmpty(true);
+        assertTrue(taskFragment.isReadyToTransit());
     }
 
     @Test
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index fe699af..a1407869 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -21,7 +21,6 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.os.Binder;
 import android.os.Bundle;
@@ -31,7 +30,6 @@
 import android.os.ResultReceiver;
 import android.text.TextUtils;
 
-import com.android.internal.telecom.ClientTransactionalServiceRepository;
 import com.android.internal.telecom.ICallControl;
 import com.android.server.telecom.flags.Flags;
 
@@ -52,20 +50,13 @@
 @SuppressLint("NotCloseable")
 public final class CallControl {
     private static final String TAG = CallControl.class.getSimpleName();
-    private static final String INTERFACE_ERROR_MSG = "Call Control is not available";
     private final String mCallId;
     private final ICallControl mServerInterface;
-    private final PhoneAccountHandle mPhoneAccountHandle;
-    private final ClientTransactionalServiceRepository mRepository;
 
     /** @hide */
-    public CallControl(@NonNull String callId, @Nullable ICallControl serverInterface,
-            @NonNull ClientTransactionalServiceRepository repository,
-            @NonNull PhoneAccountHandle pah) {
+    public CallControl(@NonNull String callId, @NonNull ICallControl serverInterface) {
         mCallId = callId;
         mServerInterface = serverInterface;
-        mRepository = repository;
-        mPhoneAccountHandle = pah;
     }
 
     /**
@@ -97,16 +88,14 @@
      */
     public void setActive(@CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<Void, CallException> callback) {
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.setActive(mCallId,
-                        new CallControlResultReceiver("setActive", executor, callback));
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        try {
+            mServerInterface.setActive(mCallId,
+                    new CallControlResultReceiver("setActive", executor, callback));
 
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -134,16 +123,12 @@
         validateVideoState(videoState);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.answer(videoState, mCallId,
-                        new CallControlResultReceiver("answer", executor, callback));
+        try {
+            mServerInterface.answer(videoState, mCallId,
+                    new CallControlResultReceiver("answer", executor, callback));
 
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -165,16 +150,14 @@
      */
     public void setInactive(@CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<Void, CallException> callback) {
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.setInactive(mCallId,
-                        new CallControlResultReceiver("setInactive", executor, callback));
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        try {
+            mServerInterface.setInactive(mCallId,
+                    new CallControlResultReceiver("setInactive", executor, callback));
 
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -213,15 +196,11 @@
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
         validateDisconnectCause(disconnectCause);
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.disconnect(mCallId, disconnectCause,
-                        new CallControlResultReceiver("disconnect", executor, callback));
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        try {
+            mServerInterface.disconnect(mCallId, disconnectCause,
+                    new CallControlResultReceiver("disconnect", executor, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -245,15 +224,13 @@
      */
     public void startCallStreaming(@CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<Void, CallException> callback) {
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.startCallStreaming(mCallId,
-                        new CallControlResultReceiver("startCallStreaming", executor, callback));
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        try {
+            mServerInterface.startCallStreaming(mCallId,
+                    new CallControlResultReceiver("startCallStreaming", executor, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -281,15 +258,11 @@
         Objects.requireNonNull(callEndpoint);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.requestCallEndpointChange(callEndpoint,
-                        new CallControlResultReceiver("endpointChange", executor, callback));
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        try {
+            mServerInterface.requestCallEndpointChange(callEndpoint,
+                    new CallControlResultReceiver("requestCallEndpointChange", executor, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -313,20 +286,16 @@
      *                 passed that details why the operation failed.
      */
     @FlaggedApi(Flags.FLAG_SET_MUTE_STATE)
-    public void setMuteState(boolean isMuted, @CallbackExecutor @NonNull Executor executor,
+    public void requestMuteState(boolean isMuted, @CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<Void, CallException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.setMuteState(isMuted,
-                        new CallControlResultReceiver("setMuteState", executor, callback));
+        try {
+            mServerInterface.setMuteState(isMuted,
+                    new CallControlResultReceiver("requestMuteState", executor, callback));
 
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -352,14 +321,10 @@
     public void sendEvent(@NonNull String event, @NonNull Bundle extras) {
         Objects.requireNonNull(event);
         Objects.requireNonNull(extras);
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.sendEvent(mCallId, event, extras);
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        try {
+            mServerInterface.sendEvent(mCallId, event, extras);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index a089f5c..63db297 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -580,6 +580,9 @@
             mExtras = phoneAccount.getExtras();
             mGroupId = phoneAccount.getGroupId();
             mSupportedAudioRoutes = phoneAccount.getSupportedAudioRoutes();
+            if (phoneAccount.hasSimultaneousCallingRestriction()) {
+                mSimultaneousCallingRestriction = phoneAccount.getSimultaneousCallingRestriction();
+            }
         }
 
         /**
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
index 71e9184..467e89c 100644
--- a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
@@ -208,8 +208,7 @@
                 if (resultCode == TELECOM_TRANSACTION_SUCCESS) {
 
                     // create the interface object that the client will interact with
-                    CallControl control = new CallControl(callId, callControl, mRepository,
-                            mPhoneAccountHandle);
+                    CallControl control = new CallControl(callId, callControl);
                     // give the client the object via the OR that was passed into addCall
                     pendingControl.onResult(control);
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 1badf67..a73c46b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9430,16 +9430,6 @@
             "missed_incoming_call_sms_originator_string_array";
 
     /**
-     * String array of Apn Type configurations.
-     * The entries should be of form "APN_TYPE_NAME:priority".
-     * priority is an integer that is sorted from highest to lowest.
-     * example: cbs:5
-     *
-     * @hide
-     */
-    public static final String KEY_APN_PRIORITY_STRING_ARRAY = "apn_priority_string_array";
-
-    /**
      * Network capability priority for determine the satisfy order in telephony. The priority is
      * from the lowest 0 to the highest 100. The long-lived network shall have the lowest priority.
      * This allows other short-lived requests like MMS requests to be established. Emergency request
@@ -10755,17 +10745,14 @@
                 TimeUnit.DAYS.toMillis(1));
         sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY,
                 new String[0]);
-        sDefaults.putStringArray(KEY_APN_PRIORITY_STRING_ARRAY, new String[] {
-                "enterprise:0", "default:1", "mms:2", "supl:2", "dun:2", "hipri:3", "fota:2",
-                "ims:2", "cbs:2", "ia:2", "emergency:2", "mcx:3", "xcap:3"
-        });
 
         // Do not modify the priority unless you know what you are doing. This will have significant
         // impacts on the order of data network setup.
         sDefaults.putStringArray(
                 KEY_TELEPHONY_NETWORK_CAPABILITY_PRIORITIES_STRING_ARRAY, new String[] {
                         "eims:90", "supl:80", "mms:70", "xcap:70", "cbs:50", "mcx:50", "fota:50",
-                        "ims:40", "dun:30", "enterprise:20", "internet:20"
+                        "ims:40", "rcs:40", "dun:30", "enterprise:20", "internet:20",
+                        "prioritize_bandwidth:20", "prioritize_latency:20"
                 });
         sDefaults.putStringArray(
                 KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY, new String[] {
@@ -10777,9 +10764,10 @@
                         // registration state changes) retry can still happen.
                         "permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|"
                                 + "-3|65543|65547|2252|2253|2254, retry_interval=2500",
-                        "capabilities=mms|supl|cbs, retry_interval=2000",
-                        "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
-                                + "5000|10000|15000|20000|40000|60000|120000|240000|"
+                        "capabilities=mms|supl|cbs|rcs, retry_interval=2000",
+                        "capabilities=internet|enterprise|dun|ims|fota|xcap|mcx|"
+                                + "prioritize_bandwidth|prioritize_latency, retry_interval="
+                                + "2500|3000|5000|10000|15000|20000|40000|60000|120000|240000|"
                                 + "600000|1200000|1800000, maximum_retries=20"
                 });
         sDefaults.putStringArray(
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
index 1ec1d5f..2f6a361 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
@@ -15,42 +15,181 @@
  */
 package com.android.hoststubgen.nativesubstitution;
 
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Predicate;
+
 public class SystemProperties_host {
+    private static final Object sLock = new Object();
+
+    /** Active system property values */
+    @GuardedBy("sLock")
+    private static Map<String, String> sValues;
+    /** Predicate tested to determine if a given key can be read. */
+    @GuardedBy("sLock")
+    private static Predicate<String> sKeyReadablePredicate;
+    /** Predicate tested to determine if a given key can be written. */
+    @GuardedBy("sLock")
+    private static Predicate<String> sKeyWritablePredicate;
+    /** Callback to trigger when values are changed */
+    @GuardedBy("sLock")
+    private static Runnable sChangeCallback;
+
+    /**
+     * Reverse mapping that provides a way back to an original key from the
+     * {@link System#identityHashCode(Object)} of {@link String#intern}.
+     */
+    @GuardedBy("sLock")
+    private static SparseArray<String> sKeyHandles = new SparseArray<>();
+
+    public static void native_init$ravenwood(Map<String, String> values,
+            Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate,
+            Runnable changeCallback) {
+        synchronized (sLock) {
+            sValues = Objects.requireNonNull(values);
+            sKeyReadablePredicate = Objects.requireNonNull(keyReadablePredicate);
+            sKeyWritablePredicate = Objects.requireNonNull(keyWritablePredicate);
+            sChangeCallback = Objects.requireNonNull(changeCallback);
+            sKeyHandles.clear();
+        }
+    }
+
+    public static void native_reset$ravenwood() {
+        synchronized (sLock) {
+            sValues = null;
+            sKeyReadablePredicate = null;
+            sKeyWritablePredicate = null;
+            sChangeCallback = null;
+            sKeyHandles.clear();
+        }
+    }
+
+    public static void native_set(String key, String val) {
+        synchronized (sLock) {
+            Objects.requireNonNull(key);
+            Preconditions.requireNonNullViaRavenwoodRule(sValues);
+            if (!sKeyWritablePredicate.test(key)) {
+                throw new IllegalArgumentException(
+                        "Write access to system property '" + key + "' denied via RavenwoodRule");
+            }
+            if (key.startsWith("ro.") && sValues.containsKey(key)) {
+                throw new IllegalArgumentException(
+                        "System property '" + key + "' already defined once; cannot redefine");
+            }
+            if ((val == null) || val.isEmpty()) {
+                sValues.remove(key);
+            } else {
+                sValues.put(key, val);
+            }
+            sChangeCallback.run();
+        }
+    }
+
     public static String native_get(String key, String def) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            Objects.requireNonNull(key);
+            Preconditions.requireNonNullViaRavenwoodRule(sValues);
+            if (!sKeyReadablePredicate.test(key)) {
+                throw new IllegalArgumentException(
+                        "Read access to system property '" + key + "' denied via RavenwoodRule");
+            }
+            return sValues.getOrDefault(key, def);
+        }
     }
+
     public static int native_get_int(String key, int def) {
-        throw new RuntimeException("Not implemented yet");
+        try {
+            return Integer.parseInt(native_get(key, ""));
+        } catch (NumberFormatException ignored) {
+            return def;
+        }
     }
+
     public static long native_get_long(String key, long def) {
-        throw new RuntimeException("Not implemented yet");
+        try {
+            return Long.parseLong(native_get(key, ""));
+        } catch (NumberFormatException ignored) {
+            return def;
+        }
     }
+
     public static boolean native_get_boolean(String key, boolean def) {
-        throw new RuntimeException("Not implemented yet");
+        return parseBoolean(native_get(key, ""), def);
     }
 
     public static long native_find(String name) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            Preconditions.requireNonNullViaRavenwoodRule(sValues);
+            if (sValues.containsKey(name)) {
+                name = name.intern();
+                final int handle = System.identityHashCode(name);
+                sKeyHandles.put(handle, name);
+                return handle;
+            } else {
+                return 0;
+            }
+        }
     }
+
     public static String native_get(long handle) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            return native_get(sKeyHandles.get((int) handle), "");
+        }
     }
+
     public static int native_get_int(long handle, int def) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            return native_get_int(sKeyHandles.get((int) handle), def);
+        }
     }
+
     public static long native_get_long(long handle, long def) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            return native_get_long(sKeyHandles.get((int) handle), def);
+        }
     }
+
     public static boolean native_get_boolean(long handle, boolean def) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            return native_get_boolean(sKeyHandles.get((int) handle), def);
+        }
     }
-    public static void native_set(String key, String def) {
-        throw new RuntimeException("Not implemented yet");
-    }
+
     public static void native_add_change_callback() {
-        throw new RuntimeException("Not implemented yet");
+        // Ignored; callback always registered via init above
     }
+
     public static void native_report_sysprop_change() {
-        throw new RuntimeException("Not implemented yet");
+        // Report through callback always registered via init above
+        synchronized (sLock) {
+            Preconditions.requireNonNullViaRavenwoodRule(sValues);
+            sChangeCallback.run();
+        }
+    }
+
+    private static boolean parseBoolean(String val, boolean def) {
+        // Matches system/libbase/include/android-base/parsebool.h
+        if (val == null) return def;
+        switch (val) {
+            case "1":
+            case "on":
+            case "true":
+            case "y":
+            case "yes":
+                return true;
+            case "0":
+            case "false":
+            case "n":
+            case "no":
+            case "off":
+                return false;
+            default:
+                return def;
+        }
     }
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
index 8ca4732..76bac92 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
@@ -24,6 +24,7 @@
         private val classes: ClassNodes,
         val aidlPolicy: FilterPolicyWithReason?,
         val featureFlagsPolicy: FilterPolicyWithReason?,
+        val syspropsPolicy: FilterPolicyWithReason?,
         fallback: OutputFilter
 ) : DelegatingFilter(fallback) {
     override fun getPolicyForClass(className: String): FilterPolicyWithReason {
@@ -33,6 +34,9 @@
         if (featureFlagsPolicy != null && classes.isFeatureFlagsClass(className)) {
             return featureFlagsPolicy
         }
+        if (syspropsPolicy != null && classes.isSyspropsClass(className)) {
+            return syspropsPolicy
+        }
         return super.getPolicyForClass(className)
     }
 }
@@ -57,3 +61,13 @@
             || className.endsWith("/FeatureFlagsImpl")
             || className.endsWith("/FakeFeatureFlagsImpl");
 }
+
+/**
+ * @return if a given class "seems like" a sysprops class.
+ */
+private fun ClassNodes.isSyspropsClass(className: String): Boolean {
+    // Matches template classes defined here:
+    // https://cs.android.com/android/platform/superproject/main/+/main:system/tools/sysprop/
+    return className.startsWith("android/sysprop/")
+            && className.endsWith("Properties")
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index d38a6e3..7fdd944 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -64,6 +64,7 @@
 
         var aidlPolicy: FilterPolicyWithReason? = null
         var featureFlagsPolicy: FilterPolicyWithReason? = null
+        var syspropsPolicy: FilterPolicyWithReason? = null
 
         try {
             BufferedReader(FileReader(filename)).use { reader ->
@@ -141,6 +142,14 @@
                                         featureFlagsPolicy =
                                                 policy.withReason("$FILTER_REASON (feature flags)")
                                     }
+                                    SpecialClass.Sysprops -> {
+                                        if (syspropsPolicy != null) {
+                                            throw ParseException(
+                                                    "Policy for sysprops already defined")
+                                        }
+                                        syspropsPolicy =
+                                                policy.withReason("$FILTER_REASON (sysprops)")
+                                    }
                                 }
                             }
                         }
@@ -205,10 +214,10 @@
         }
 
         var ret: OutputFilter = imf
-        if (aidlPolicy != null || featureFlagsPolicy != null) {
+        if (aidlPolicy != null || featureFlagsPolicy != null || syspropsPolicy != null) {
             log.d("AndroidHeuristicsFilter enabled")
             ret = AndroidHeuristicsFilter(
-                    classes, aidlPolicy, featureFlagsPolicy, imf)
+                    classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, imf)
         }
         return ret
     }
@@ -218,6 +227,7 @@
     NotSpecial,
     Aidl,
     FeatureFlags,
+    Sysprops,
 }
 
 private fun resolveSpecialClass(className: String): SpecialClass {
@@ -227,6 +237,7 @@
     when (className.lowercase()) {
         ":aidl" -> return SpecialClass.Aidl
         ":feature_flags" -> return SpecialClass.FeatureFlags
+        ":sysprops" -> return SpecialClass.Sysprops
     }
     throw ParseException("Invalid special class name \"$className\"")
 }