Merge "Add the LoudnessCodecApi tests to presubmit" into main
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
index e5a06c9..3c361d7 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
@@ -16,12 +16,14 @@
 
 package android.graphics.perftests;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.Color;
+import android.graphics.ColorSpace;
 import android.graphics.ImageDecoder;
 import android.graphics.Paint;
 import android.graphics.RecordingCanvas;
@@ -104,15 +106,36 @@
     }
 
     @Test
-    public void testCreateScaledBitmap() throws IOException {
+    public void testCreateScaledSrgbBitmap() throws IOException {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         final Context context = InstrumentationRegistry.getContext();
         Bitmap source = ImageDecoder.decodeBitmap(
                 ImageDecoder.createSource(context.getResources(), R.drawable.fountain_night),
                 (decoder, info, source1) -> {
                     decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+                    decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.SRGB));
                 });
         source.setGainmap(null);
+        assertEquals(source.getColorSpace().getId(), ColorSpace.Named.SRGB.ordinal());
+
+        while (state.keepRunning()) {
+            Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true)
+                    .recycle();
+        }
+    }
+
+    @Test
+    public void testCreateScaledP3Bitmap() throws IOException {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final Context context = InstrumentationRegistry.getContext();
+        Bitmap source = ImageDecoder.decodeBitmap(
+                ImageDecoder.createSource(context.getResources(), R.drawable.fountain_night),
+                (decoder, info, source1) -> {
+                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+                    decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+                });
+        source.setGainmap(null);
+        assertEquals(source.getColorSpace().getId(), ColorSpace.Named.DISPLAY_P3.ordinal());
 
         while (state.keepRunning()) {
             Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true)
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
index 4352c8a..ea10690 100644
--- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
+++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
@@ -24,11 +24,11 @@
 import android.content.res.TypedArray
 import android.perftests.utils.BenchmarkState
 import android.perftests.utils.PerfStatusReporter
-import android.util.ArraySet
 import androidx.test.filters.LargeTest
 import com.android.internal.pm.parsing.pkg.PackageImpl
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils
 import com.android.internal.util.ConcurrentUtils
+import com.android.server.SystemConfig
 import java.io.File
 import java.io.FileOutputStream
 import java.util.concurrent.ArrayBlockingQueue
@@ -217,8 +217,10 @@
                     isCoreApp,
                     this,
                 )
-                override fun getHiddenApiWhitelistedApps() = ArraySet<String>()
-                override fun getInstallConstraintsAllowlist() = ArraySet<String>()
+                override fun getHiddenApiWhitelistedApps() =
+                        SystemConfig.getInstance().hiddenApiWhitelistedApps
+                override fun getInstallConstraintsAllowlist() =
+                        SystemConfig.getInstance().installConstraintsAllowlist
             })
 
         override fun parseImpl(file: File) =
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 385fd50..14195c4 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -241,6 +241,23 @@
     public static final int CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE = 3;
 
     /**
+     * Broadcast action: sent to apps that hold the status bar permission when
+     * KeyguardManager#setPrivateNotificationsAllowed() is changed.
+     *
+     * Extras: #EXTRA_KM_PRIVATE_NOTIFS_ALLOWED
+     * @hide
+     */
+    public static final String ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED
+            = "android.app.action.KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED";
+
+    /**
+     * Boolean, the latest value of KeyguardManager#getPrivateNotificationsAllowed()
+     * @hide
+     */
+    public static final String EXTRA_KM_PRIVATE_NOTIFS_ALLOWED
+            = "android.app.extra.KM_PRIVATE_NOTIFS_ALLOWED";
+
+    /**
      * Session flag for {@link #registerSessionListener} indicating the listener
      * is interested in sessions on the keygaurd.
      * Keyguard Session Boundaries:
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index fb0edb9..d11c6c5 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -36,3 +36,10 @@
   description: "Guards the security fix that ensures all URIs in intents and Person.java are valid"
   bug: "281044385"
 }
+
+flag {
+  name: "keyguard_private_notifications"
+  namespace: "systemui"
+  description: "Fixes the behavior of KeyguardManager#setPrivateNotificationsAllowed()"
+  bug: "309920145"
+}
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
index 3ce094e..cbb0ae7 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -23,6 +23,7 @@
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.app.ResultInfo;
+import android.content.Context;
 import android.content.res.CompatibilityInfo;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -85,6 +86,12 @@
         client.reportRelaunch(r);
     }
 
+    @Nullable
+    @Override
+    public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
+        return client.getActivity(getActivityToken());
+    }
+
     // ObjectPoolItem implementation
 
     private ActivityRelaunchItem() {}
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index d2ef65a..1190bf6 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -24,12 +24,14 @@
 import android.annotation.Nullable;
 import android.app.ActivityClient;
 import android.app.ActivityOptions;
+import android.app.ActivityThread;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.app.IActivityClientController;
 import android.app.ProfilerInfo;
 import android.app.ResultInfo;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.res.CompatibilityInfo;
@@ -115,6 +117,13 @@
         client.countLaunchingActivities(-1);
     }
 
+    @Nullable
+    @Override
+    public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
+        // LaunchActivityItem may update the global config with #mCurConfig.
+        return ActivityThread.currentApplication();
+    }
+
     // ObjectPoolItem implementation
 
     private LaunchActivityItem() {}
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index 961da19..1353d16 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
+import android.content.Context;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.os.IBinder;
@@ -55,6 +56,12 @@
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
+    @Nullable
+    @Override
+    public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
+        return client.getActivity(getActivityToken());
+    }
+
     // ObjectPoolItem implementation
 
     private MoveToDisplayItem() {}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index ee48e43..5dd4eb7 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -41,6 +41,7 @@
 import android.content.res.Configuration;
 import android.os.IBinder;
 import android.os.Process;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Slog;
@@ -62,8 +63,11 @@
     private final PendingTransactionActions mPendingActions = new PendingTransactionActions();
     private final TransactionExecutorHelper mHelper = new TransactionExecutorHelper();
 
-    /** Keeps track of display ids whose Configuration got updated within a transaction. */
-    private final ArraySet<Integer> mConfigUpdatedDisplayIds = new ArraySet<>();
+    /**
+     * Keeps track of the Context whose Configuration got updated within a transaction, mapping to
+     * the config before the transaction.
+     */
+    private final ArrayMap<Context, Configuration> mContextToPreChangedConfigMap = new ArrayMap<>();
 
     /** Initialize an instance with transaction handler, that will execute all requested actions. */
     public TransactionExecutor(@NonNull ClientTransactionHandler clientTransactionHandler) {
@@ -91,16 +95,35 @@
             executeLifecycleState(transaction);
         }
 
-        if (!mConfigUpdatedDisplayIds.isEmpty()) {
+        if (!mContextToPreChangedConfigMap.isEmpty()) {
             // Whether this transaction should trigger DisplayListener#onDisplayChanged.
-            final ClientTransactionListenerController controller =
-                    ClientTransactionListenerController.getInstance();
-            final int displayCount = mConfigUpdatedDisplayIds.size();
-            for (int i = 0; i < displayCount; i++) {
-                final int displayId = mConfigUpdatedDisplayIds.valueAt(i);
-                controller.onDisplayChanged(displayId);
+            try {
+                // Calculate display ids that have config changed.
+                final ArraySet<Integer> configUpdatedDisplayIds = new ArraySet<>();
+                final int contextCount = mContextToPreChangedConfigMap.size();
+                for (int i = 0; i < contextCount; i++) {
+                    final Context context = mContextToPreChangedConfigMap.keyAt(i);
+                    final Configuration preTransactionConfig =
+                            mContextToPreChangedConfigMap.valueAt(i);
+                    final Configuration postTransactionConfig = context.getResources()
+                            .getConfiguration();
+                    if (!areConfigurationsEqualForDisplay(
+                            postTransactionConfig, preTransactionConfig)) {
+                        configUpdatedDisplayIds.add(context.getDisplayId());
+                    }
+                }
+
+                // Dispatch the display changed callbacks.
+                final ClientTransactionListenerController controller =
+                        ClientTransactionListenerController.getInstance();
+                final int displayCount = configUpdatedDisplayIds.size();
+                for (int i = 0; i < displayCount; i++) {
+                    final int displayId = configUpdatedDisplayIds.valueAt(i);
+                    controller.onDisplayChanged(displayId);
+                }
+            } finally {
+                mContextToPreChangedConfigMap.clear();
             }
-            mConfigUpdatedDisplayIds.clear();
         }
 
         mPendingActions.clear();
@@ -182,26 +205,24 @@
             }
         }
 
-        // Can't read flag from isolated process.
-        final boolean isBundleClientTransactionFlagEnabled = !Process.isIsolated()
-                && bundleClientTransactionFlag();
-        final Context configUpdatedContext = isBundleClientTransactionFlagEnabled
+        final boolean shouldTrackConfigUpdatedContext =
+                // No configuration change for local transaction.
+                !mTransactionHandler.isExecutingLocalTransaction()
+                        // Can't read flag from isolated process.
+                        && !Process.isIsolated()
+                        && bundleClientTransactionFlag();
+        final Context configUpdatedContext = shouldTrackConfigUpdatedContext
                 ? item.getContextToUpdate(mTransactionHandler)
                 : null;
-        final Configuration preExecutedConfig = configUpdatedContext != null
-                ? new Configuration(configUpdatedContext.getResources().getConfiguration())
-                : null;
+        if (configUpdatedContext != null
+                && !mContextToPreChangedConfigMap.containsKey(configUpdatedContext)) {
+            // Keep track of the first pre-executed config of each changed Context.
+            mContextToPreChangedConfigMap.put(configUpdatedContext,
+                    new Configuration(configUpdatedContext.getResources().getConfiguration()));
+        }
 
         item.execute(mTransactionHandler, mPendingActions);
 
-        if (configUpdatedContext != null) {
-            final Configuration postExecutedConfig = configUpdatedContext.getResources()
-                    .getConfiguration();
-            if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) {
-                mConfigUpdatedDisplayIds.add(configUpdatedContext.getDisplayId());
-            }
-        }
-
         item.postExecute(mTransactionHandler, mPendingActions);
         if (r == null) {
             // Launch activity request will create an activity record.
diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java
index 7d3eb87..193b03c 100644
--- a/core/java/android/app/servertransaction/WindowStateResizeItem.java
+++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java
@@ -22,9 +22,12 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityThread;
 import android.app.ClientTransactionHandler;
+import android.content.Context;
 import android.os.Parcel;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.util.MergedConfiguration;
 import android.view.IWindow;
 import android.view.InsetsState;
@@ -52,6 +55,11 @@
     @Override
     public void execute(@NonNull ClientTransactionHandler client,
             @NonNull PendingTransactionActions pendingActions) {
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
+                mReportDraw ? "windowResizedReport" : "windowResized");
+        if (mWindow instanceof ResizeListener listener) {
+            listener.onExecutingWindowStateResizeItem();
+        }
         try {
             mWindow.resized(mFrames, mReportDraw, mConfiguration, mInsetsState, mForceLayout,
                     mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing);
@@ -59,6 +67,14 @@
             // Should be a local call.
             throw new RuntimeException(e);
         }
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+    }
+
+    @Nullable
+    @Override
+    public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
+        // WindowStateResizeItem may update the global config with #mConfiguration.
+        return ActivityThread.currentApplication();
     }
 
     // ObjectPoolItem implementation
@@ -80,7 +96,7 @@
         instance.mFrames = new ClientWindowFrames(frames);
         instance.mReportDraw = reportDraw;
         instance.mConfiguration = new MergedConfiguration(configuration);
-        instance.mInsetsState = new InsetsState(insetsState);
+        instance.mInsetsState = new InsetsState(insetsState, true /* copySources */);
         instance.mForceLayout = forceLayout;
         instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
         instance.mDisplayId = displayId;
@@ -190,4 +206,10 @@
                 + ", configuration=" + mConfiguration
                 + "}";
     }
+
+    /** The interface for IWindow to perform resize directly if possible. */
+    public interface ResizeListener {
+        /** Notifies that IWindow#resized is going to be called from WindowStateResizeItem. */
+        void onExecutingWindowStateResizeItem();
+    }
 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b75c64d..fa76e39 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3547,6 +3547,8 @@
      *
      * @param receiver The BroadcastReceiver to unregister.
      *
+     * @throws IllegalArgumentException if the {@code receiver} was not previously registered or
+     *                                  already unregistered.
      * @see #registerReceiver
      */
     public abstract void unregisterReceiver(BroadcastReceiver receiver);
diff --git a/core/java/android/net/LocalSocket.java b/core/java/android/net/LocalSocket.java
index b69410c..a86396c 100644
--- a/core/java/android/net/LocalSocket.java
+++ b/core/java/android/net/LocalSocket.java
@@ -196,7 +196,8 @@
     }
 
     /**
-     * Retrieves the input stream for this instance.
+     * Retrieves the input stream for this instance. Closing this stream is equivalent to closing
+     * the entire socket and its associated streams using {@link #close()}.
      *
      * @return input stream
      * @throws IOException if socket has been closed or cannot be created.
@@ -207,7 +208,8 @@
     }
 
     /**
-     * Retrieves the output stream for this instance.
+     * Retrieves the output stream for this instance. Closing this stream is equivalent to closing
+     * the entire socket and its associated streams using {@link #close()}.
      *
      * @return output stream
      * @throws IOException if socket has been closed or cannot be created.
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 78248d9..e1965ef 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -53,7 +53,6 @@
 import android.view.contentcapture.DataRemovalRequest;
 import android.view.contentcapture.DataShareRequest;
 import android.view.contentcapture.IContentCaptureDirectManager;
-import android.view.contentcapture.MainContentCaptureSession;
 
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.FrameworkStatsLog;
@@ -724,7 +723,7 @@
             final Bundle extras;
             if (binder != null) {
                 extras = new Bundle();
-                extras.putBinder(MainContentCaptureSession.EXTRA_BINDER, binder);
+                extras.putBinder(ContentCaptureSession.EXTRA_BINDER, binder);
             } else {
                 extras = null;
             }
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index f7b9aa2..079991a 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -76,7 +76,7 @@
             Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW,
                     "InsetsAsyncAnimation: " + WindowInsets.Type.toString(runner.getTypes()),
                     runner.getTypes());
-            releaseControls(mControl.getControls());
+            InsetsController.releaseControls(mControl.getControls());
             mMainThreadHandler.post(() ->
                     mOuterCallbacks.notifyFinished(InsetsAnimationThreadControlRunner.this, shown));
         }
@@ -130,12 +130,6 @@
         });
     }
 
-    private void releaseControls(SparseArray<InsetsSourceControl> controls) {
-        for (int i = controls.size() - 1; i >= 0; i--) {
-            controls.valueAt(i).release(SurfaceControl::release);
-        }
-    }
-
     @Override
     @UiThread
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 147c15b..dd09157 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1353,6 +1353,9 @@
                 });
             }
 
+            // The leashes are copied, but they won't be used.
+            releaseControls(controls);
+
             // The requested visibilities should be delayed as well. Otherwise, we might override
             // the insets visibility before playing animation.
             setRequestedVisibleTypes(mReportedRequestedVisibleTypes, types);
@@ -1422,6 +1425,12 @@
         }
     }
 
+    static void releaseControls(SparseArray<InsetsSourceControl> controls) {
+        for (int i = controls.size() - 1; i >= 0; i--) {
+            controls.valueAt(i).release(SurfaceControl::release);
+        }
+    }
+
     // TODO(b/242962223): Make this setter restrictive.
     @Override
     public void setSystemDrivenInsetsAnimationLoggingListener(
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ec99459..442ea66 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -10760,11 +10760,11 @@
             return;
         }
 
-        session.internalNotifyViewTreeEvent(/* started= */ true);
+        session.notifyViewTreeEvent(/* started= */ true);
         try {
             dispatchProvideContentCaptureStructure();
         } finally {
-            session.internalNotifyViewTreeEvent(/* started= */ false);
+            session.notifyViewTreeEvent(/* started= */ false);
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1f81a64..32afe06 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -111,6 +111,7 @@
 import android.app.ResourcesManager;
 import android.app.WindowConfiguration;
 import android.app.compat.CompatChanges;
+import android.app.servertransaction.WindowStateResizeItem;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ClipData;
 import android.content.ClipDescription;
@@ -208,7 +209,6 @@
 import android.view.autofill.AutofillManager;
 import android.view.contentcapture.ContentCaptureManager;
 import android.view.contentcapture.ContentCaptureSession;
-import android.view.contentcapture.MainContentCaptureSession;
 import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Scroller;
@@ -2012,26 +2012,24 @@
     }
 
     /** Handles messages {@link #MSG_RESIZED} and {@link #MSG_RESIZED_REPORT}. */
-    private void handleResized(int msg, SomeArgs args) {
+    private void handleResized(ClientWindowFrames frames, boolean reportDraw,
+            MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout,
+            boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) {
         if (!mAdded) {
             return;
         }
 
-        final ClientWindowFrames frames = (ClientWindowFrames) args.arg1;
-        final MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg2;
         CompatibilityInfo.applyOverrideScaleIfNeeded(mergedConfiguration);
-        final boolean forceNextWindowRelayout = args.argi1 != 0;
-        final int displayId = args.argi3;
-        final boolean dragResizing = args.argi5 != 0;
-
         final Rect frame = frames.frame;
         final Rect displayFrame = frames.displayFrame;
         final Rect attachedFrame = frames.attachedFrame;
         if (mTranslator != null) {
+            mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
             mTranslator.translateRectInScreenToAppWindow(frame);
             mTranslator.translateRectInScreenToAppWindow(displayFrame);
             mTranslator.translateRectInScreenToAppWindow(attachedFrame);
         }
+        mInsetsController.onStateChanged(insetsState);
         final float compatScale = frames.compatScale;
         final boolean frameChanged = !mWinFrame.equals(frame);
         final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration);
@@ -2040,8 +2038,8 @@
         final boolean displayChanged = mDisplay.getDisplayId() != displayId;
         final boolean compatScaleChanged = mTmpFrames.compatScale != compatScale;
         final boolean dragResizingChanged = mPendingDragResizing != dragResizing;
-        if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
-                && !displayChanged && !forceNextWindowRelayout
+        if (!reportDraw && !frameChanged && !configChanged && !attachedFrameChanged
+                && !displayChanged && !forceLayout
                 && !compatScaleChanged && !dragResizingChanged) {
             return;
         }
@@ -2073,11 +2071,11 @@
             }
         }
 
-        mForceNextWindowRelayout |= forceNextWindowRelayout;
-        mPendingAlwaysConsumeSystemBars = args.argi2 != 0;
-        mSyncSeqId = args.argi4 > mSyncSeqId ? args.argi4 : mSyncSeqId;
+        mForceNextWindowRelayout |= forceLayout;
+        mPendingAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+        mSyncSeqId = syncSeqId > mSyncSeqId ? syncSeqId : mSyncSeqId;
 
-        if (msg == MSG_RESIZED_REPORT) {
+        if (reportDraw) {
             reportNextDraw("resized");
         }
 
@@ -4104,7 +4102,7 @@
 
         final ContentCaptureManager manager = mAttachInfo.mContentCaptureManager;
         if (manager != null && mAttachInfo.mContentCaptureEvents != null) {
-            final MainContentCaptureSession session = manager.getMainContentCaptureSession();
+            final ContentCaptureSession session = manager.getMainContentCaptureSession();
             session.notifyContentCaptureEvents(mAttachInfo.mContentCaptureEvents);
         }
         mAttachInfo.mContentCaptureEvents = null;
@@ -5021,7 +5019,7 @@
 
             // Initial dispatch of window bounds to content capture
             if (mAttachInfo.mContentCaptureManager != null) {
-                MainContentCaptureSession session =
+                ContentCaptureSession session =
                         mAttachInfo.mContentCaptureManager.getMainContentCaptureSession();
                 session.notifyWindowBoundsChanged(session.getId(),
                         getConfiguration().windowConfiguration.getBounds());
@@ -6232,8 +6230,17 @@
                 case MSG_RESIZED:
                 case MSG_RESIZED_REPORT: {
                     final SomeArgs args = (SomeArgs) msg.obj;
-                    mInsetsController.onStateChanged((InsetsState) args.arg3);
-                    handleResized(msg.what, args);
+                    final ClientWindowFrames frames = (ClientWindowFrames) args.arg1;
+                    final boolean reportDraw = msg.what == MSG_RESIZED_REPORT;
+                    final MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg2;
+                    final InsetsState insetsState = (InsetsState) args.arg3;
+                    final boolean forceLayout = args.argi1 != 0;
+                    final boolean alwaysConsumeSystemBars = args.argi2 != 0;
+                    final int displayId = args.argi3;
+                    final int syncSeqId = args.argi4;
+                    final boolean dragResizing = args.argi5 != 0;
+                    handleResized(frames, reportDraw, mergedConfiguration, insetsState, forceLayout,
+                            alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
                     args.recycle();
                     break;
                 }
@@ -8797,7 +8804,7 @@
         mSurfaceControl.setTransformHint(transformHint);
 
         if (mAttachInfo.mContentCaptureManager != null) {
-            MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
+            ContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
                     .getMainContentCaptureSession();
             mainSession.notifyWindowBoundsChanged(mainSession.getId(),
                     getConfiguration().windowConfiguration.getBounds());
@@ -9379,20 +9386,8 @@
             boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) {
         Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
         SomeArgs args = SomeArgs.obtain();
-        final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid());
-        if (sameProcessCall) {
-            insetsState = new InsetsState(insetsState, true /* copySource */);
-        }
-        if (mTranslator != null) {
-            mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
-        }
-        if (insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) {
-            ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchResized",
-                    getInsetsController().getHost().getInputMethodManager(), null /* icProto */);
-        }
-        args.arg1 = sameProcessCall ? new ClientWindowFrames(frames) : frames;
-        args.arg2 = sameProcessCall && mergedConfiguration != null
-                ? new MergedConfiguration(mergedConfiguration) : mergedConfiguration;
+        args.arg1 = frames;
+        args.arg2 = mergedConfiguration;
         args.arg3 = insetsState;
         args.argi1 = forceLayout ? 1 : 0;
         args.argi2 = alwaysConsumeSystemBars ? 1 : 0;
@@ -10815,9 +10810,10 @@
         }
     }
 
-    static class W extends IWindow.Stub {
+    static class W extends IWindow.Stub implements WindowStateResizeItem.ResizeListener {
         private final WeakReference<ViewRootImpl> mViewAncestor;
         private final IWindowSession mWindowSession;
+        private boolean mIsFromResizeItem;
 
         W(ViewRootImpl viewAncestor) {
             mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
@@ -10825,17 +10821,46 @@
         }
 
         @Override
+        public void onExecutingWindowStateResizeItem() {
+            mIsFromResizeItem = true;
+        }
+
+        @Override
         public void resized(ClientWindowFrames frames, boolean reportDraw,
                 MergedConfiguration mergedConfiguration, InsetsState insetsState,
                 boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
                 boolean dragResizing) {
+            final boolean isFromResizeItem = mIsFromResizeItem;
+            mIsFromResizeItem = false;
             // Although this is a AIDL method, it will only be triggered in local process through
             // either WindowStateResizeItem or WindowlessWindowManager.
             final ViewRootImpl viewAncestor = mViewAncestor.get();
-            if (viewAncestor != null) {
-                viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState,
-                        forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
+            if (viewAncestor == null) {
+                return;
             }
+            if (insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) {
+                ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#resized",
+                        viewAncestor.getInsetsController().getHost().getInputMethodManager(),
+                        null /* icProto */);
+            }
+            // If the UI thread is the same as the current thread that is dispatching
+            // WindowStateResizeItem, then it can run directly.
+            if (isFromResizeItem && viewAncestor.mHandler.getLooper()
+                    == ActivityThread.currentActivityThread().getLooper()) {
+                viewAncestor.handleResized(frames, reportDraw, mergedConfiguration, insetsState,
+                        forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
+                return;
+            }
+            // The the parameters from WindowStateResizeItem are already copied.
+            final boolean needCopy =
+                    !isFromResizeItem && (Binder.getCallingPid() == Process.myPid());
+            if (needCopy) {
+                insetsState = new InsetsState(insetsState, true /* copySource */);
+                frames = new ClientWindowFrames(frames);
+                mergedConfiguration = new MergedConfiguration(mergedConfiguration);
+            }
+            viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState,
+                    forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
         }
 
         @Override
@@ -11910,6 +11935,12 @@
 
                 if (syncBuffer) {
                     boolean result = mBlastBufferQueue.syncNextTransaction(transaction -> {
+                        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);
+                        transaction.addTransactionCommittedListener(mSimpleExecutor,
+                                () -> mHandler.removeCallbacks(timeoutRunnable));
                         surfaceSyncGroup.addTransaction(transaction);
                         surfaceSyncGroup.markSyncReady();
                     });
diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
index 44b4353..9efa144 100644
--- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
@@ -17,10 +17,16 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.SparseArray;
 import android.view.autofill.AutofillId;
 import android.view.contentcapture.ViewNode.ViewStructureImpl;
 
+import java.util.ArrayList;
+
 /**
  * A session that is explicitly created by the app (and hence is a descendant of
  * {@link MainContentCaptureSession}).
@@ -40,7 +46,7 @@
     }
 
     @Override
-    MainContentCaptureSession getMainCaptureSession() {
+    ContentCaptureSession getMainCaptureSession() {
         if (mParent instanceof MainContentCaptureSession) {
             return (MainContentCaptureSession) mParent;
         }
@@ -48,9 +54,25 @@
     }
 
     @Override
+    void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
+            @NonNull ComponentName component, int flags) {
+        getMainCaptureSession().start(token, shareableActivityToken, component, flags);
+    }
+
+    @Override
+    boolean isDisabled() {
+        return getMainCaptureSession().isDisabled();
+    }
+
+    @Override
+    boolean setDisabled(boolean disabled) {
+        return getMainCaptureSession().setDisabled(disabled);
+    }
+
+    @Override
     ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
         final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
-        getMainCaptureSession().notifyChildSessionStarted(mId, child.mId, clientContext);
+        internalNotifyChildSessionStarted(mId, child.mId, clientContext);
         return child;
     }
 
@@ -61,51 +83,80 @@
 
     @Override
     public void updateContentCaptureContext(@Nullable ContentCaptureContext context) {
-        getMainCaptureSession().notifyContextUpdated(mId, context);
+        internalNotifyContextUpdated(mId, context);
     }
 
     @Override
     void onDestroy() {
-        getMainCaptureSession().notifyChildSessionFinished(mParent.mId, mId);
+        internalNotifyChildSessionFinished(mParent.mId, mId);
     }
 
     @Override
-    void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
-        getMainCaptureSession().notifyViewAppeared(mId, node);
+    void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId,
+            @NonNull ContentCaptureContext clientContext) {
+        getMainCaptureSession()
+                .internalNotifyChildSessionStarted(parentSessionId, childSessionId, clientContext);
     }
 
     @Override
-    void internalNotifyViewDisappeared(@NonNull AutofillId id) {
-        getMainCaptureSession().notifyViewDisappeared(mId, id);
+    void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId) {
+        getMainCaptureSession().internalNotifyChildSessionFinished(parentSessionId, childSessionId);
     }
 
     @Override
-    void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text) {
-        getMainCaptureSession().notifyViewTextChanged(mId, id, text);
+    void internalNotifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
+        getMainCaptureSession().internalNotifyContextUpdated(sessionId, context);
     }
 
     @Override
-    void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets) {
-        getMainCaptureSession().notifyViewInsetsChanged(mId, viewInsets);
+    void internalNotifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
+        getMainCaptureSession().internalNotifyViewAppeared(sessionId, node);
     }
 
     @Override
-    public void internalNotifyViewTreeEvent(boolean started) {
-        getMainCaptureSession().notifyViewTreeEvent(mId, started);
+    void internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
+        getMainCaptureSession().internalNotifyViewDisappeared(sessionId, id);
+    }
+
+    @Override
+    void internalNotifyViewTextChanged(
+            int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
+        getMainCaptureSession().internalNotifyViewTextChanged(sessionId, id, text);
+    }
+
+    @Override
+    void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
+        getMainCaptureSession().internalNotifyViewInsetsChanged(mId, viewInsets);
+    }
+
+    @Override
+    public void internalNotifyViewTreeEvent(int sessionId, boolean started) {
+        getMainCaptureSession().internalNotifyViewTreeEvent(sessionId, started);
     }
 
     @Override
     void internalNotifySessionResumed() {
-        getMainCaptureSession().notifySessionResumed();
+        getMainCaptureSession().internalNotifySessionResumed();
     }
 
     @Override
     void internalNotifySessionPaused() {
-        getMainCaptureSession().notifySessionPaused();
+        getMainCaptureSession().internalNotifySessionPaused();
     }
 
     @Override
     boolean isContentCaptureEnabled() {
         return getMainCaptureSession().isContentCaptureEnabled();
     }
+
+    @Override
+    public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) {
+        getMainCaptureSession().notifyWindowBoundsChanged(sessionId, bounds);
+    }
+
+    @Override
+    public void notifyContentCaptureEvents(
+            @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) {
+        getMainCaptureSession().notifyContentCaptureEvents(contentCaptureEvents);
+    }
 }
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index a829747..daf07a3 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -502,7 +502,7 @@
     private Handler mHandler;
 
     @GuardedBy("mLock")
-    private MainContentCaptureSession mMainSession;
+    private ContentCaptureSession mMainSession;
 
     @Nullable // set on-demand by addDumpable()
     private Dumper mDumpable;
@@ -587,7 +587,7 @@
      */
     @NonNull
     @UiThread
-    public MainContentCaptureSession getMainContentCaptureSession() {
+    public ContentCaptureSession getMainContentCaptureSession() {
         synchronized (mLock) {
             if (mMainSession == null) {
                 mMainSession = new MainContentCaptureSession(
@@ -726,7 +726,7 @@
     public boolean isContentCaptureEnabled() {
         if (mOptions.lite) return false;
 
-        final MainContentCaptureSession mainSession;
+        final ContentCaptureSession mainSession;
         synchronized (mLock) {
             mainSession = mMainSession;
         }
@@ -777,7 +777,7 @@
             Log.d(TAG, "setContentCaptureEnabled(): setting to " + enabled + " for " + mContext);
         }
 
-        MainContentCaptureSession mainSession;
+        ContentCaptureSession mainSession;
         synchronized (mLock) {
             if (enabled) {
                 mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_APP;
@@ -803,7 +803,7 @@
         final boolean flagSecureEnabled =
                 (params.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0;
 
-        MainContentCaptureSession mainSession;
+        ContentCaptureSession mainSession;
         boolean alreadyDisabledByApp;
         synchronized (mLock) {
             alreadyDisabledByApp = (mFlags & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0;
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index bb815c0..0ca36ba2 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -27,9 +27,13 @@
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
+import android.content.ComponentName;
 import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.IBinder;
 import android.util.DebugUtils;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewStructure;
 import android.view.autofill.AutofillId;
@@ -37,6 +41,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 
@@ -60,6 +65,18 @@
     private static final SecureRandom ID_GENERATOR = new SecureRandom();
 
     /**
+     * Name of the {@link IResultReceiver} extra used to pass the binder interface to the service.
+     * @hide
+     */
+    public static final String EXTRA_BINDER = "binder";
+
+    /**
+     * Name of the {@link IResultReceiver} extra used to pass the content capture enabled state.
+     * @hide
+     */
+    public static final String EXTRA_ENABLED_STATE = "enabled";
+
+    /**
      * Initial state, when there is no session.
      *
      * @hide
@@ -262,7 +279,19 @@
 
     /** @hide */
     @NonNull
-    abstract MainContentCaptureSession getMainCaptureSession();
+    abstract ContentCaptureSession getMainCaptureSession();
+
+    abstract void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
+            @NonNull ComponentName component, int flags);
+
+    abstract boolean isDisabled();
+
+    /**
+     * Sets the disabled state of content capture.
+     *
+     * @return whether disabled state was changed.
+     */
+    abstract boolean setDisabled(boolean disabled);
 
     /**
      * Gets the id used to identify this session.
@@ -400,10 +429,11 @@
             throw new IllegalArgumentException("Invalid node class: " + node.getClass());
         }
 
-        internalNotifyViewAppeared((ViewStructureImpl) node);
+        internalNotifyViewAppeared(mId, (ViewStructureImpl) node);
     }
 
-    abstract void internalNotifyViewAppeared(@NonNull ViewNode.ViewStructureImpl node);
+    abstract void internalNotifyViewAppeared(
+            int sessionId, @NonNull ViewNode.ViewStructureImpl node);
 
     /**
      * Notifies the Content Capture Service that a node has been removed from the view structure.
@@ -420,10 +450,10 @@
         Objects.requireNonNull(id);
         if (!isContentCaptureEnabled()) return;
 
-        internalNotifyViewDisappeared(id);
+        internalNotifyViewDisappeared(mId, id);
     }
 
-    abstract void internalNotifyViewDisappeared(@NonNull AutofillId id);
+    abstract void internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id);
 
     /**
      * Notifies the Content Capture Service that a list of nodes has appeared in the view structure.
@@ -445,12 +475,12 @@
             }
         }
 
-        internalNotifyViewTreeEvent(/* started= */ true);
+        internalNotifyViewTreeEvent(mId, /* started= */ true);
         for (int i = 0; i < appearedNodes.size(); i++) {
             ViewStructure v = appearedNodes.get(i);
-            internalNotifyViewAppeared((ViewStructureImpl) v);
+            internalNotifyViewAppeared(mId, (ViewStructureImpl) v);
         }
-        internalNotifyViewTreeEvent(/* started= */ false);
+        internalNotifyViewTreeEvent(mId, /* started= */ false);
     }
 
     /**
@@ -476,15 +506,15 @@
         if (!isContentCaptureEnabled()) return;
 
         if (CompatChanges.isChangeEnabled(NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS)) {
-            internalNotifyViewTreeEvent(/* started= */ true);
+            internalNotifyViewTreeEvent(mId, /* started= */ true);
         }
         // TODO(b/123036895): use a internalNotifyViewsDisappeared that optimizes how the event is
         // parcelized
         for (long id : virtualIds) {
-            internalNotifyViewDisappeared(new AutofillId(hostId, id, mId));
+            internalNotifyViewDisappeared(mId, new AutofillId(hostId, id, mId));
         }
         if (CompatChanges.isChangeEnabled(NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS)) {
-            internalNotifyViewTreeEvent(/* started= */ false);
+            internalNotifyViewTreeEvent(mId, /* started= */ false);
         }
     }
 
@@ -499,10 +529,10 @@
 
         if (!isContentCaptureEnabled()) return;
 
-        internalNotifyViewTextChanged(id, text);
+        internalNotifyViewTextChanged(mId, id, text);
     }
 
-    abstract void internalNotifyViewTextChanged(@NonNull AutofillId id,
+    abstract void internalNotifyViewTextChanged(int sessionId, @NonNull AutofillId id,
             @Nullable CharSequence text);
 
     /**
@@ -513,13 +543,18 @@
 
         if (!isContentCaptureEnabled()) return;
 
-        internalNotifyViewInsetsChanged(viewInsets);
+        internalNotifyViewInsetsChanged(mId, viewInsets);
     }
 
-    abstract void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets);
+    abstract void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets);
 
     /** @hide */
-    public abstract void internalNotifyViewTreeEvent(boolean started);
+    public void notifyViewTreeEvent(boolean started) {
+        internalNotifyViewTreeEvent(mId, started);
+    }
+
+    /** @hide */
+    abstract void internalNotifyViewTreeEvent(int sessionId, boolean started);
 
     /**
      * Notifies the Content Capture Service that a session has resumed.
@@ -543,6 +578,21 @@
 
     abstract void internalNotifySessionPaused();
 
+    abstract void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId,
+            @NonNull ContentCaptureContext clientContext);
+
+    abstract void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId);
+
+    abstract void internalNotifyContextUpdated(
+            int sessionId, @Nullable ContentCaptureContext context);
+
+    /** @hide */
+    public abstract void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds);
+
+    /** @hide */
+    public abstract void notifyContentCaptureEvents(
+            @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents);
+
     /**
      * Creates a {@link ViewStructure} for a "standard" view.
      *
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 19ba316..9e604d0 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -82,6 +82,7 @@
  *
  * @hide
  */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public final class MainContentCaptureSession extends ContentCaptureSession {
 
     private static final String TAG = MainContentCaptureSession.class.getSimpleName();
@@ -97,18 +98,6 @@
      */
     private static final int MSG_FLUSH = 1;
 
-    /**
-     * Name of the {@link IResultReceiver} extra used to pass the binder interface to the service.
-     * @hide
-     */
-    public static final String EXTRA_BINDER = "binder";
-
-    /**
-     * Name of the {@link IResultReceiver} extra used to pass the content capture enabled state.
-     * @hide
-     */
-    public static final String EXTRA_ENABLED_STATE = "enabled";
-
     @NonNull
     private final AtomicBoolean mDisabled = new AtomicBoolean(false);
 
@@ -254,20 +243,21 @@
     }
 
     @Override
-    MainContentCaptureSession getMainCaptureSession() {
+    ContentCaptureSession getMainCaptureSession() {
         return this;
     }
 
     @Override
     ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
         final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
-        notifyChildSessionStarted(mId, child.mId, clientContext);
+        internalNotifyChildSessionStarted(mId, child.mId, clientContext);
         return child;
     }
 
     /**
      * Starts this session.
      */
+    @Override
     void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
             @NonNull ComponentName component, int flags) {
         if (runOnBackgroundThreadEnabled()) {
@@ -617,7 +607,8 @@
                     + flushFrequencyMs + "ms: " + TimeUtils.logTimeOfDay(mNextFlush));
         }
         // Post using a Runnable directly to trim a few μs from PooledLambda.obtainMessage()
-        mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs);
+        mHandler.postDelayed(() ->
+                flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs);
     }
 
     private void flushIfNeeded(@FlushReason int reason) {
@@ -703,7 +694,7 @@
 
     @Override
     public void updateContentCaptureContext(@Nullable ContentCaptureContext context) {
-        notifyContextUpdated(mId, context);
+        internalNotifyContextUpdated(mId, context);
     }
 
     /**
@@ -781,91 +772,22 @@
     }
 
     @Override
-    void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
-        notifyViewAppeared(mId, node);
-    }
-
-    @Override
-    void internalNotifyViewDisappeared(@NonNull AutofillId id) {
-        notifyViewDisappeared(mId, id);
-    }
-
-    @Override
-    void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text) {
-        notifyViewTextChanged(mId, id, text);
-    }
-
-    @Override
-    void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets) {
-        notifyViewInsetsChanged(mId, viewInsets);
-    }
-
-    @Override
-    public void internalNotifyViewTreeEvent(boolean started) {
-        notifyViewTreeEvent(mId, started);
-    }
-
-    @Override
-    public void internalNotifySessionResumed() {
-        notifySessionResumed(mId);
-    }
-
-    @Override
-    public void internalNotifySessionPaused() {
-        notifySessionPaused(mId);
-    }
-
-    @Override
-    boolean isContentCaptureEnabled() {
-        return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled();
-    }
-
-    // Called by ContentCaptureManager.isContentCaptureEnabled
-    boolean isDisabled() {
-        return mDisabled.get();
-    }
-
-    /**
-     * Sets the disabled state of content capture.
-     *
-     * @return whether disabled state was changed.
-     */
-    boolean setDisabled(boolean disabled) {
-        return mDisabled.compareAndSet(!disabled, disabled);
-    }
-
-    // TODO(b/122454205): refactor "notifyXXXX" methods below to a common "Buffer" object that is
-    // shared between ActivityContentCaptureSession and ChildContentCaptureSession objects. Such
-    // change should also get get rid of the "internalNotifyXXXX" methods above
-    void notifyChildSessionStarted(int parentSessionId, int childSessionId,
-            @NonNull ContentCaptureContext clientContext) {
-        final ContentCaptureEvent event =
-                new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
-                        .setParentSessionId(parentSessionId)
-                        .setClientContext(clientContext);
-        enqueueEvent(event, FORCE_FLUSH);
-    }
-
-    void notifyChildSessionFinished(int parentSessionId, int childSessionId) {
-        final ContentCaptureEvent event =
-                new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
-                        .setParentSessionId(parentSessionId);
-        enqueueEvent(event, FORCE_FLUSH);
-    }
-
-    void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
+    void internalNotifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
         final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
                 .setViewNode(node.mNode);
         enqueueEvent(event);
     }
 
-    void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
+    @Override
+    void internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
         final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED)
                 .setAutofillId(id);
         enqueueEvent(event);
     }
 
-    void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
+    @Override
+    void internalNotifyViewTextChanged(
+            int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
         // Since the same CharSequence instance may be reused in the TextView, we need to make
         // a copy of its content so that its value will not be changed by subsequent updates
         // in the TextView.
@@ -894,14 +816,16 @@
         enqueueEvent(event);
     }
 
-    void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
+    @Override
+    void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
         final ContentCaptureEvent event =
                 new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
                         .setInsets(viewInsets);
         enqueueEvent(event);
     }
 
-    void notifyViewTreeEvent(int sessionId, boolean started) {
+    @Override
+    public void internalNotifyViewTreeEvent(int sessionId, boolean started) {
         final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED;
         final boolean disableFlush = mManager.getFlushViewTreeAppearingEventDisabled();
         final boolean forceFlush = disableFlush ? !started : FORCE_FLUSH;
@@ -910,23 +834,59 @@
         enqueueEvent(event, forceFlush);
     }
 
-    void notifySessionResumed(int sessionId) {
-        final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED);
+    @Override
+    public void internalNotifySessionResumed() {
+        final ContentCaptureEvent event = new ContentCaptureEvent(mId, TYPE_SESSION_RESUMED);
         enqueueEvent(event, FORCE_FLUSH);
     }
 
-    void notifySessionPaused(int sessionId) {
-        final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED);
+    @Override
+    public void internalNotifySessionPaused() {
+        final ContentCaptureEvent event = new ContentCaptureEvent(mId, TYPE_SESSION_PAUSED);
         enqueueEvent(event, FORCE_FLUSH);
     }
 
-    void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
+    @Override
+    boolean isContentCaptureEnabled() {
+        return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled();
+    }
+
+    @Override
+    boolean isDisabled() {
+        return mDisabled.get();
+    }
+
+    @Override
+    boolean setDisabled(boolean disabled) {
+        return mDisabled.compareAndSet(!disabled, disabled);
+    }
+
+    @Override
+    void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId,
+            @NonNull ContentCaptureContext clientContext) {
+        final ContentCaptureEvent event =
+                new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
+                        .setParentSessionId(parentSessionId)
+                        .setClientContext(clientContext);
+        enqueueEvent(event, FORCE_FLUSH);
+    }
+
+    @Override
+    void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId) {
+        final ContentCaptureEvent event =
+                new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
+                        .setParentSessionId(parentSessionId);
+        enqueueEvent(event, FORCE_FLUSH);
+    }
+
+    @Override
+    void internalNotifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
         final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
                 .setClientContext(context);
         enqueueEvent(event, FORCE_FLUSH);
     }
 
-    /** public because is also used by ViewRootImpl */
+    @Override
     public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) {
         final ContentCaptureEvent event =
                 new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED)
@@ -972,11 +932,12 @@
         }
     }
 
-    /** public because is also used by ViewRootImpl */
+    @Override
     public void notifyContentCaptureEvents(
             @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) {
         if (runOnBackgroundThreadEnabled()) {
-            runOnContentCaptureThread(() -> notifyContentCaptureEventsImpl(contentCaptureEvents));
+            runOnContentCaptureThread(() ->
+                    notifyContentCaptureEventsImpl(contentCaptureEvents));
         } else {
             // Preserve the control arm behaviour.
             notifyContentCaptureEventsImpl(contentCaptureEvents);
@@ -992,12 +953,12 @@
             }
             for (int i = 0; i < contentCaptureEvents.size(); i++) {
                 int sessionId = contentCaptureEvents.keyAt(i);
-                notifyViewTreeEvent(sessionId, /* started= */ true);
+                internalNotifyViewTreeEvent(sessionId, /* started= */ true);
                 ArrayList<Object> events = contentCaptureEvents.valueAt(i);
                 for_each_event: for (int j = 0; j < events.size(); j++) {
                     Object event = events.get(j);
                     if (event instanceof AutofillId) {
-                        notifyViewDisappeared(sessionId, (AutofillId) event);
+                        internalNotifyViewDisappeared(sessionId, (AutofillId) event);
                     } else if (event instanceof View) {
                         View view = (View) event;
                         ContentCaptureSession session = view.getContentCaptureSession();
@@ -1015,12 +976,12 @@
                         view.onProvideContentCaptureStructure(structure, /* flags= */ 0);
                         session.notifyViewAppeared(structure);
                     } else if (event instanceof Insets) {
-                        notifyViewInsetsChanged(sessionId, (Insets) event);
+                        internalNotifyViewInsetsChanged(sessionId, (Insets) event);
                     } else {
                         Log.w(TAG, "invalid content capture event: " + event);
                     }
                 }
-                notifyViewTreeEvent(sessionId, /* started= */ false);
+                internalNotifyViewTreeEvent(sessionId, /* started= */ false);
             }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
diff --git a/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java b/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java
index f05d9cb..983658a 100644
--- a/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java
+++ b/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java
@@ -96,8 +96,10 @@
         info.baseRevisionCode = pkg.getBaseRevisionCode();
         info.splitRevisionCodes = pkg.getSplitRevisionCodes();
         info.versionName = pkg.getVersionName();
-        info.sharedUserId = pkg.getSharedUserId();
-        info.sharedUserLabel = pkg.getSharedUserLabelResourceId();
+        if (!pkg.isLeavingSharedUser()) {
+            info.sharedUserId = pkg.getSharedUserId();
+            info.sharedUserLabel = pkg.getSharedUserLabelResourceId();
+        }
         info.applicationInfo = applicationInfo;
         info.installLocation = pkg.getInstallLocation();
         if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c6209dd..232a36f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -578,6 +578,7 @@
     <protected-broadcast android:name="com.android.settings.network.SWITCH_TO_SUBSCRIPTION" />
     <protected-broadcast android:name="com.android.settings.wifi.action.NETWORK_REQUEST" />
 
+    <protected-broadcast android:name="android.app.action.KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED" />
     <protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
     <protected-broadcast android:name="NotificationHistoryDatabase.CLEANUP" />
     <protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
diff --git a/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java
deleted file mode 100644
index 785a8a1..0000000
--- a/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java
+++ /dev/null
@@ -1,73 +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 android.app.servertransaction;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.doReturn;
-
-import android.app.Activity;
-import android.app.ClientTransactionHandler;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.os.IBinder;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link ActivityConfigurationChangeItem}.
- *
- * Build/Install/Run:
- *  atest FrameworksCoreTests:ActivityConfigurationChangeItemTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class ActivityConfigurationChangeItemTest {
-
-    @Mock
-    private ClientTransactionHandler mHandler;
-    @Mock
-    private IBinder mToken;
-    @Mock
-    private Activity mActivity;
-    // Can't mock final class.
-    private final Configuration mConfiguration = new Configuration();
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void testGetContextToUpdate() {
-        doReturn(mActivity).when(mHandler).getActivity(mToken);
-
-        final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem
-                .obtain(mToken, mConfiguration);
-        final Context context = item.getContextToUpdate(mHandler);
-
-        assertEquals(mActivity, context);
-    }
-}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
new file mode 100644
index 0000000..b5e8203
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.app.Activity;
+import android.app.ActivityThread;
+import android.app.ClientTransactionHandler;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
+import android.util.MergedConfiguration;
+import android.view.IWindow;
+import android.view.InsetsState;
+import android.window.ClientWindowFrames;
+import android.window.WindowContext;
+import android.window.WindowContextInfo;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for subtypes of {@link ClientTransactionItem}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksCoreTests:ClientTransactionItemTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class ClientTransactionItemTest {
+
+    @Mock
+    private ClientTransactionHandler mHandler;
+    @Mock
+    private IBinder mActivityToken;
+    @Mock
+    private Activity mActivity;
+    @Mock
+    private PendingTransactionActions mPendingActions;
+    @Mock
+    private IBinder mWindowClientToken;
+    @Mock
+    private WindowContext mWindowContext;
+    @Mock
+    private IWindow mWindow;
+
+    // Can't mock final class.
+    private Configuration mGlobalConfig;
+    private Configuration mConfiguration;
+    private ActivityThread.ActivityClientRecord mActivityClientRecord;
+    private ArrayMap<IBinder, DestroyActivityItem> mActivitiesToBeDestroyed;
+    private InsetsState mInsetsState;
+    private ClientWindowFrames mFrames;
+    private MergedConfiguration mMergedConfiguration;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mGlobalConfig = new Configuration();
+        mConfiguration = new Configuration();
+        mActivitiesToBeDestroyed = new ArrayMap<>();
+        mActivityClientRecord = new ActivityThread.ActivityClientRecord();
+        mInsetsState = new InsetsState();
+        mFrames = new ClientWindowFrames();
+        mMergedConfiguration = new MergedConfiguration(mGlobalConfig, mConfiguration);
+
+        doReturn(mActivity).when(mHandler).getActivity(mActivityToken);
+        doReturn(mActivitiesToBeDestroyed).when(mHandler).getActivitiesToBeDestroyed();
+    }
+
+    @Test
+    public void testActivityConfigurationChangeItem_getContextToUpdate() {
+        final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem
+                .obtain(mActivityToken, mConfiguration);
+        final Context context = item.getContextToUpdate(mHandler);
+
+        assertEquals(mActivity, context);
+    }
+
+    @Test
+    public void testActivityRelaunchItem_getContextToUpdate() {
+        final ActivityRelaunchItem item = ActivityRelaunchItem
+                .obtain(mActivityToken, null /* pendingResults */, null  /* pendingNewIntents */,
+                        0 /* configChange */, mMergedConfiguration, false /* preserveWindow */);
+        final Context context = item.getContextToUpdate(mHandler);
+
+        assertEquals(mActivity, context);
+    }
+
+    @Test
+    public void testConfigurationChangeItem_getContextToUpdate() {
+        final ConfigurationChangeItem item = ConfigurationChangeItem
+                .obtain(mConfiguration, DEVICE_ID_DEFAULT);
+        final Context context = item.getContextToUpdate(mHandler);
+
+        assertEquals(ActivityThread.currentApplication(), context);
+    }
+
+    @Test
+    public void testDestroyActivityItem_preExecute() {
+        final DestroyActivityItem item = DestroyActivityItem
+                .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+        item.preExecute(mHandler);
+
+        assertEquals(1, mActivitiesToBeDestroyed.size());
+        assertEquals(item, mActivitiesToBeDestroyed.get(mActivityToken));
+    }
+
+    @Test
+    public void testDestroyActivityItem_postExecute() {
+        final DestroyActivityItem item = DestroyActivityItem
+                .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+        item.preExecute(mHandler);
+        item.postExecute(mHandler, mPendingActions);
+
+        assertTrue(mActivitiesToBeDestroyed.isEmpty());
+    }
+
+    @Test
+    public void testDestroyActivityItem_execute() {
+        final DestroyActivityItem item = DestroyActivityItem
+                .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+        item.execute(mHandler, mActivityClientRecord, mPendingActions);
+
+        verify(mHandler).handleDestroyActivity(eq(mActivityClientRecord), eq(false) /* finishing */,
+                eq(123) /* configChanges */, eq(false) /* getNonConfigInstance */, any());
+    }
+
+    @Test
+    public void testLaunchActivityItem_getContextToUpdate() {
+        final LaunchActivityItem item = new TestUtils.LaunchActivityItemBuilder(
+                mActivityToken, new Intent(), new ActivityInfo())
+                .build();
+
+        final Context context = item.getContextToUpdate(mHandler);
+
+        assertEquals(ActivityThread.currentApplication(), context);
+    }
+
+    @Test
+    public void testMoveToDisplayItem_getContextToUpdate() {
+        final MoveToDisplayItem item = MoveToDisplayItem
+                .obtain(mActivityToken, DEFAULT_DISPLAY, mConfiguration);
+        final Context context = item.getContextToUpdate(mHandler);
+
+        assertEquals(mActivity, context);
+    }
+
+    @Test
+    public void testWindowContextInfoChangeItem_execute() {
+        final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
+                .obtain(mWindowClientToken, mConfiguration, DEFAULT_DISPLAY);
+        item.execute(mHandler, mPendingActions);
+
+        verify(mHandler).handleWindowContextInfoChanged(mWindowClientToken,
+                new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY));
+    }
+
+    @Test
+    public void testWindowContextInfoChangeItem_getContextToUpdate() {
+        doReturn(mWindowContext).when(mHandler).getWindowContext(mWindowClientToken);
+
+        final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
+                .obtain(mWindowClientToken, mConfiguration, DEFAULT_DISPLAY);
+        final Context context = item.getContextToUpdate(mHandler);
+
+        assertEquals(mWindowContext, context);
+    }
+
+    @Test
+    public void testWindowContextWindowRemovalItem_execute() {
+        final WindowContextWindowRemovalItem item = WindowContextWindowRemovalItem.obtain(
+                mWindowClientToken);
+        item.execute(mHandler, mPendingActions);
+
+        verify(mHandler).handleWindowContextWindowRemoval(mWindowClientToken);
+    }
+
+    @Test
+    public void testWindowStateResizeItem_execute() throws RemoteException {
+        final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames,
+                true /* reportDraw */, mMergedConfiguration, mInsetsState, true /* forceLayout */,
+                true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
+                true /* dragResizing */);
+        item.execute(mHandler, mPendingActions);
+
+        verify(mWindow).resized(mFrames,
+                true /* reportDraw */, mMergedConfiguration, mInsetsState, true /* forceLayout */,
+                true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
+                true /* dragResizing */);
+    }
+
+    @Test
+    public void testWindowStateResizeItem_getContextToUpdate() {
+        final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames,
+                true /* reportDraw */, mMergedConfiguration, mInsetsState, true /* forceLayout */,
+                true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
+                true /* dragResizing */);
+        final Context context = item.getContextToUpdate(mHandler);
+
+        assertEquals(ActivityThread.currentApplication(), context);
+    }
+
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ConfigurationChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ConfigurationChangeItemTest.java
deleted file mode 100644
index d9f5523..0000000
--- a/core/tests/coretests/src/android/app/servertransaction/ConfigurationChangeItemTest.java
+++ /dev/null
@@ -1,67 +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 android.app.servertransaction;
-
-import static android.content.Context.DEVICE_ID_DEFAULT;
-
-import static org.junit.Assert.assertEquals;
-
-import android.app.ActivityThread;
-import android.app.ClientTransactionHandler;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link ConfigurationChangeItem}.
- *
- * Build/Install/Run:
- *  atest FrameworksCoreTests:ConfigurationChangeItemTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class ConfigurationChangeItemTest {
-
-    @Mock
-    private ClientTransactionHandler mHandler;
-    // Can't mock final class.
-    private final Configuration mConfiguration = new Configuration();
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void testGetContextToUpdate() {
-        final ConfigurationChangeItem item = ConfigurationChangeItem
-                .obtain(mConfiguration, DEVICE_ID_DEFAULT);
-        final Context context = item.getContextToUpdate(mHandler);
-
-        assertEquals(ActivityThread.currentApplication(), context);
-    }
-}
diff --git a/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java b/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java
deleted file mode 100644
index ecd75a8..0000000
--- a/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java
+++ /dev/null
@@ -1,99 +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 android.app.servertransaction;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import android.app.ActivityThread.ActivityClientRecord;
-import android.app.ClientTransactionHandler;
-import android.os.IBinder;
-import android.platform.test.annotations.Presubmit;
-import android.util.ArrayMap;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link DestroyActivityItem}.
- *
- * Build/Install/Run:
- *  atest FrameworksCoreTests:DestroyActivityItemTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class DestroyActivityItemTest {
-
-    @Mock
-    private ClientTransactionHandler mHandler;
-    @Mock
-    private PendingTransactionActions mPendingActions;
-    @Mock
-    private IBinder mActivityToken;
-
-    // Can't mock final class.
-    private ActivityClientRecord mActivityClientRecord;
-
-    private ArrayMap<IBinder, DestroyActivityItem> mActivitiesToBeDestroyed;
-    private DestroyActivityItem mItem;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        mItem = DestroyActivityItem.obtain(
-                mActivityToken, false /* finished */, 123 /* configChanges */);
-        mActivityClientRecord = new ActivityClientRecord();
-        mActivitiesToBeDestroyed = new ArrayMap<>();
-
-        doReturn(mActivitiesToBeDestroyed).when(mHandler).getActivitiesToBeDestroyed();
-    }
-
-    @Test
-    public void testPreExecute() {
-        mItem.preExecute(mHandler);
-
-        assertEquals(1, mActivitiesToBeDestroyed.size());
-        assertEquals(mItem, mActivitiesToBeDestroyed.get(mActivityToken));
-    }
-
-    @Test
-    public void testPostExecute() {
-        mItem.preExecute(mHandler);
-        mItem.postExecute(mHandler, mPendingActions);
-
-        assertTrue(mActivitiesToBeDestroyed.isEmpty());
-    }
-
-    @Test
-    public void testExecute() {
-        mItem.execute(mHandler, mActivityClientRecord, mPendingActions);
-
-        verify(mHandler).handleDestroyActivity(eq(mActivityClientRecord), eq(false) /* finishing */,
-                eq(123) /* configChanges */, eq(false) /* getNonConfigInstance */, any());
-    }
-}
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java
deleted file mode 100644
index a801a76..0000000
--- a/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java
+++ /dev/null
@@ -1,89 +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 android.app.servertransaction;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import android.app.ClientTransactionHandler;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.os.IBinder;
-import android.platform.test.annotations.Presubmit;
-import android.window.WindowContext;
-import android.window.WindowContextInfo;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link WindowContextInfoChangeItem}.
- *
- * Build/Install/Run:
- *  atest FrameworksCoreTests:WindowContextInfoChangeItemTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class WindowContextInfoChangeItemTest {
-
-    @Mock
-    private ClientTransactionHandler mHandler;
-    @Mock
-    private PendingTransactionActions mPendingActions;
-    @Mock
-    private IBinder mClientToken;
-    @Mock
-    private WindowContext mWindowContext;
-    // Can't mock final class.
-    private final Configuration mConfiguration = new Configuration();
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void testExecute() {
-        final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
-                .obtain(mClientToken, mConfiguration, DEFAULT_DISPLAY);
-        item.execute(mHandler, mPendingActions);
-
-        verify(mHandler).handleWindowContextInfoChanged(mClientToken,
-                new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY));
-    }
-
-    @Test
-    public void testGetContextToUpdate() {
-        doReturn(mWindowContext).when(mHandler).getWindowContext(mClientToken);
-
-        final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
-                .obtain(mClientToken, mConfiguration, DEFAULT_DISPLAY);
-        final Context context = item.getContextToUpdate(mHandler);
-
-        assertEquals(mWindowContext, context);
-    }
-}
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java
deleted file mode 100644
index cf9935f..0000000
--- a/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java
+++ /dev/null
@@ -1,65 +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 android.app.servertransaction;
-
-import static org.mockito.Mockito.verify;
-
-import android.app.ClientTransactionHandler;
-import android.os.IBinder;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link WindowContextWindowRemovalItem}.
- *
- * Build/Install/Run:
- *  atest FrameworksCoreTests:WindowContextWindowRemovalItemTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class WindowContextWindowRemovalItemTest {
-
-    @Mock
-    private ClientTransactionHandler mHandler;
-    @Mock
-    private PendingTransactionActions mPendingActions;
-    @Mock
-    private IBinder mClientToken;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void testExecute() {
-        final WindowContextWindowRemovalItem item = WindowContextWindowRemovalItem.obtain(
-                mClientToken);
-        item.execute(mHandler, mPendingActions);
-
-        verify(mHandler).handleWindowContextWindowRemoval(mClientToken);
-    }
-}
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
deleted file mode 100644
index 4d45daf..0000000
--- a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
+++ /dev/null
@@ -1,82 +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 android.app.servertransaction;
-
-import static org.mockito.Mockito.verify;
-
-import android.app.ClientTransactionHandler;
-import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
-import android.util.MergedConfiguration;
-import android.view.IWindow;
-import android.view.InsetsState;
-import android.window.ClientWindowFrames;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link WindowStateResizeItem}.
- *
- * Build/Install/Run:
- *  atest FrameworksCoreTests:WindowStateResizeItemTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class WindowStateResizeItemTest {
-
-    @Mock
-    private ClientTransactionHandler mHandler;
-    @Mock
-    private PendingTransactionActions mPendingActions;
-    @Mock
-    private IWindow mWindow;
-
-    private InsetsState mInsetsState;
-    private ClientWindowFrames mFrames;
-    private MergedConfiguration mConfiguration;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        mInsetsState = new InsetsState();
-        mFrames = new ClientWindowFrames();
-        mConfiguration = new MergedConfiguration();
-    }
-
-    @Test
-    public void testExecute() throws RemoteException {
-        final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames,
-                true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */,
-                true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
-                true /* dragResizing */);
-        item.execute(mHandler, mPendingActions);
-
-        verify(mWindow).resized(mFrames,
-                true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */,
-                true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
-                true /* dragResizing */);
-    }
-}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
index 35ddfdb..e52aa1b 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
@@ -153,7 +153,7 @@
         final ContentCaptureManager manager =
                 new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
         // Ensure main session is created.
-        final MainContentCaptureSession unused = manager.getMainContentCaptureSession();
+        final ContentCaptureSession unused = manager.getMainContentCaptureSession();
         final WindowManager.LayoutParams initialParam = new WindowManager.LayoutParams();
         initialParam.flags |= WindowManager.LayoutParams.FLAG_SECURE;
 
@@ -167,7 +167,7 @@
         final ContentCaptureManager manager =
                 new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
         // Ensure main session is created.
-        final MainContentCaptureSession unused = manager.getMainContentCaptureSession();
+        final ContentCaptureSession unused = manager.getMainContentCaptureSession();
         final WindowManager.LayoutParams initialParam = new WindowManager.LayoutParams();
         initialParam.flags |= WindowManager.LayoutParams.FLAG_SECURE;
         // Default param does not have FLAG_SECURE set.
@@ -184,7 +184,7 @@
         final ContentCaptureManager manager =
                 new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
         // Ensure main session is created.
-        final MainContentCaptureSession unused = manager.getMainContentCaptureSession();
+        final ContentCaptureSession unused = manager.getMainContentCaptureSession();
         // Default param does not have FLAG_SECURE set.
         final WindowManager.LayoutParams resetParam = new WindowManager.LayoutParams();
 
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index 23b9b9b..4a4c693 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -21,12 +21,19 @@
 import static org.testng.Assert.assertThrows;
 
 import android.compat.testing.PlatformCompatChangeRule;
+import android.content.ComponentName;
 import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewStructure;
 import android.view.autofill.AutofillId;
 import android.view.contentcapture.ViewNode.ViewStructureImpl;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.google.common.collect.ImmutableMap;
 
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
@@ -40,6 +47,7 @@
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.util.ArrayList;
 import java.util.Map;
 
 /**
@@ -195,6 +203,22 @@
         }
 
         @Override
+        void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
+                @NonNull ComponentName component, int flags) {
+            throw new UnsupportedOperationException("should not have been called");
+        }
+
+        @Override
+        boolean isDisabled() {
+            throw new UnsupportedOperationException("should not have been called");
+        }
+
+        @Override
+        boolean setDisabled(boolean disabled) {
+            throw new UnsupportedOperationException("should not have been called");
+        }
+
+        @Override
         ContentCaptureSession newChild(ContentCaptureContext context) {
             throw new UnsupportedOperationException("should not have been called");
         }
@@ -210,20 +234,20 @@
         }
 
         @Override
-        void internalNotifyViewAppeared(ViewStructureImpl node) {
+        void internalNotifyViewAppeared(final int sessionId, ViewStructureImpl node) {
             throw new UnsupportedOperationException("should not have been called");
         }
 
         @Override
-        void internalNotifyViewDisappeared(AutofillId id) {}
+        void internalNotifyViewDisappeared(final int sessionId, AutofillId id) {}
 
         @Override
-        void internalNotifyViewTextChanged(AutofillId id, CharSequence text) {
+        void internalNotifyViewTextChanged(final int sessionId, AutofillId id, CharSequence text) {
             throw new UnsupportedOperationException("should not have been called");
         }
 
         @Override
-        public void internalNotifyViewTreeEvent(boolean started) {
+        public void internalNotifyViewTreeEvent(final int sessionId, boolean started) {
             if (started) {
                 mInternalNotifyViewTreeEventStartedCount += 1;
             } else {
@@ -242,7 +266,34 @@
         }
 
         @Override
-        void internalNotifyViewInsetsChanged(Insets viewInsets) {
+        void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId,
+                @NonNull ContentCaptureContext clientContext) {
+            throw new UnsupportedOperationException("should not have been called");
+        }
+
+        @Override
+        void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId) {
+            throw new UnsupportedOperationException("should not have been called");
+        }
+
+        @Override
+        void internalNotifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
+            throw new UnsupportedOperationException("should not have been called");
+        }
+
+        @Override
+        public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) {
+            throw new UnsupportedOperationException("should not have been called");
+        }
+
+        @Override
+        public void notifyContentCaptureEvents(
+                @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) {
+
+        }
+
+        @Override
+        void internalNotifyViewInsetsChanged(final int sessionId, Insets viewInsets) {
             throw new UnsupportedOperationException("should not have been called");
         }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 15ee4e1..65597de 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -47,6 +47,7 @@
 import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
 import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
 
+import android.annotation.CallbackExecutor;
 import android.app.Activity;
 import android.app.ActivityClient;
 import android.app.ActivityOptions;
@@ -63,6 +64,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.SystemProperties;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
@@ -85,6 +87,7 @@
 import androidx.window.extensions.WindowExtensionsImpl;
 import androidx.window.extensions.core.util.function.Consumer;
 import androidx.window.extensions.core.util.function.Function;
+import androidx.window.extensions.core.util.function.Predicate;
 import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
 import androidx.window.extensions.layout.WindowLayoutComponentImpl;
 
@@ -158,8 +161,20 @@
     /** Callback to Jetpack to notify about changes to split states. */
     @GuardedBy("mLock")
     @Nullable
-    private Consumer<List<SplitInfo>> mEmbeddingCallback;
+    private Consumer<List<SplitInfo>> mSplitInfoCallback;
     private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
+
+    /**
+     * Stores callbacks to Jetpack to notify about changes to {@link ActivityStack activityStacks}
+     * and corresponding {@link Executor executors} to dispatch the callback.
+     */
+    @GuardedBy("mLock")
+    @NonNull
+    private final ArrayMap<Consumer<List<ActivityStack>>, Executor> mActivityStackCallbacks =
+            new ArrayMap<>();
+
+    private final List<ActivityStack> mLastReportedActivityStacks = new ArrayList<>();
+
     private final Handler mHandler;
     final Object mLock = new Object();
     private final ActivityStartMonitor mActivityStartMonitor;
@@ -283,7 +298,7 @@
     }
 
     @Override
-    public void unpinTopActivityStack(int taskId){
+    public void unpinTopActivityStack(int taskId) {
         synchronized (mLock) {
             Log.i(TAG, "Request to unpin top activity stack.");
             final TaskContainer task = getTaskContainer(taskId);
@@ -333,6 +348,9 @@
     public void setActivityStackAttributesCalculator(
             @NonNull Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes>
                     calculator) {
+        if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+            return;
+        }
         synchronized (mLock) {
             mActivityStackAttributesCalculator = calculator;
         }
@@ -340,6 +358,9 @@
 
     @Override
     public void clearActivityStackAttributesCalculator() {
+        if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+            return;
+        }
         synchronized (mLock) {
             mActivityStackAttributesCalculator = null;
         }
@@ -351,7 +372,7 @@
         return mSplitAttributesCalculator;
     }
 
-    @Override
+    // TODO(b/295993745): remove after we migrate to the bundle approach.
     @NonNull
     public ActivityOptions setLaunchingActivityStack(@NonNull ActivityOptions options,
             @NonNull IBinder token) {
@@ -368,6 +389,7 @@
 
     /**
      * Registers the split organizer callback to notify about changes to active splits.
+     *
      * @deprecated Use {@link #setSplitInfoCallback(Consumer)} starting with
      * {@link WindowExtensionsImpl#getVendorApiLevel()} 2.
      */
@@ -381,12 +403,14 @@
 
     /**
      * Registers the split organizer callback to notify about changes to active splits.
+     *
      * @since {@link WindowExtensionsImpl#getVendorApiLevel()} 2
      */
+    @Override
     public void setSplitInfoCallback(Consumer<List<SplitInfo>> callback) {
         synchronized (mLock) {
-            mEmbeddingCallback = callback;
-            updateCallbackIfNecessary();
+            mSplitInfoCallback = callback;
+            updateSplitInfoCallbackIfNecessary();
         }
     }
 
@@ -396,7 +420,35 @@
     @Override
     public void clearSplitInfoCallback() {
         synchronized (mLock) {
-            mEmbeddingCallback = null;
+            mSplitInfoCallback = null;
+        }
+    }
+
+    /**
+     * Registers the callback for the {@link ActivityStack} state change.
+     *
+     * @param executor The executor to dispatch the callback.
+     * @param callback The callback for this {@link ActivityStack} state change.
+     */
+    @Override
+    public void registerActivityStackCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<List<ActivityStack>> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        synchronized (mLock) {
+            mActivityStackCallbacks.put(callback, executor);
+            updateActivityStackCallbackIfNecessary();
+        }
+    }
+
+    /** @see #registerActivityStackCallback(Executor, Consumer) */
+    @Override
+    public void unregisterActivityStackCallback(@NonNull Consumer<List<ActivityStack>> callback) {
+        Objects.requireNonNull(callback);
+
+        synchronized (mLock) {
+            mActivityStackCallbacks.remove(callback);
         }
     }
 
@@ -408,13 +460,11 @@
         synchronized (mLock) {
             // Translate ActivityStack to TaskFragmentContainer.
             final List<TaskFragmentContainer> pendingFinishingContainers =
-                    activityStackTokens.stream()
-                    .map(token -> {
+                    activityStackTokens.stream().map(token -> {
                         synchronized (mLock) {
                             return getContainer(token);
                         }
-                    }).filter(Objects::nonNull)
-                    .toList();
+                    }).filter(Objects::nonNull).toList();
 
             if (pendingFinishingContainers.isEmpty()) {
                 return;
@@ -497,6 +547,68 @@
         }
     }
 
+    @Override
+    public void updateActivityStackAttributes(@NonNull IBinder activityStackToken,
+                                              @NonNull ActivityStackAttributes attributes) {
+        if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+            return;
+        }
+        Objects.requireNonNull(activityStackToken);
+        Objects.requireNonNull(attributes);
+
+        synchronized (mLock) {
+            final TaskFragmentContainer container = getContainer(activityStackToken);
+            if (container == null) {
+                Log.w(TAG, "Cannot find TaskFragmentContainer for token:" + activityStackToken);
+                return;
+            }
+            if (!container.isOverlay()) {
+                Log.w(TAG, "Updating non-overlay container has not supported yet!");
+                return;
+            }
+
+            final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+            final WindowContainerTransaction wct = transactionRecord.getTransaction();
+            mPresenter.applyActivityStackAttributes(wct, container, attributes);
+            transactionRecord.apply(false /* shouldApplyIndependently */);
+        }
+    }
+
+    @Override
+    @Nullable
+    public ParentContainerInfo getParentContainerInfo(@NonNull IBinder activityStackToken) {
+        if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+            return null;
+        }
+        Objects.requireNonNull(activityStackToken);
+        synchronized (mLock) {
+            final TaskFragmentContainer container = getContainer(activityStackToken);
+            if (container == null) {
+                return null;
+            }
+            final TaskContainer.TaskProperties properties = container.getTaskContainer()
+                    .getTaskProperties();
+            return mPresenter.createParentContainerInfoFromTaskProperties(properties);
+        }
+    }
+
+    @Override
+    @Nullable
+    public IBinder getActivityStackToken(@NonNull String tag) {
+        if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+            return null;
+        }
+        Objects.requireNonNull(tag);
+        synchronized (mLock) {
+            final TaskFragmentContainer taskFragmentContainer =
+                    getContainer(container -> tag.equals(container.getOverlayTag()));
+            if (taskFragmentContainer == null) {
+                return null;
+            }
+            return taskFragmentContainer.getTaskFragmentToken();
+        }
+    }
+
     /**
      * Called when the transaction is ready so that the organizer can update the TaskFragments based
      * on the changes in transaction.
@@ -565,8 +677,9 @@
     /**
      * Called when a TaskFragment is created and organized by this organizer.
      *
-     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
-     * @param taskFragmentInfo  Info of the TaskFragment that is created.
+     * @param wct              The {@link WindowContainerTransaction} to make any changes with if
+     *                         needed.
+     * @param taskFragmentInfo Info of the TaskFragment that is created.
      */
     // Suppress GuardedBy warning because lint ask to mark this method as
     // @GuardedBy(container.mController.mLock), which is mLock itself
@@ -574,7 +687,7 @@
     @VisibleForTesting
     @GuardedBy("mLock")
     void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
-            @NonNull TaskFragmentInfo taskFragmentInfo) {
+                                @NonNull TaskFragmentInfo taskFragmentInfo) {
         final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
         if (container == null) {
             return;
@@ -594,8 +707,9 @@
     /**
      * Called when the status of an organized TaskFragment is changed.
      *
-     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
-     * @param taskFragmentInfo  Info of the TaskFragment that is changed.
+     * @param wct              The {@link WindowContainerTransaction} to make any changes with if
+     *                         needed.
+     * @param taskFragmentInfo Info of the TaskFragment that is changed.
      */
     // Suppress GuardedBy warning because lint ask to mark this method as
     // @GuardedBy(container.mController.mLock), which is mLock itself
@@ -665,8 +779,9 @@
     /**
      * Called when an organized TaskFragment is removed.
      *
-     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
-     * @param taskFragmentInfo  Info of the TaskFragment that is removed.
+     * @param wct              The {@link WindowContainerTransaction} to make any changes with if
+     *                         needed.
+     * @param taskFragmentInfo Info of the TaskFragment that is removed.
      */
     @VisibleForTesting
     @GuardedBy("mLock")
@@ -686,14 +801,14 @@
      * Called when the parent leaf Task of organized TaskFragments is changed.
      * When the leaf Task is changed, the organizer may want to update the TaskFragments in one
      * transaction.
-     *
+     * <p>
      * For case like screen size change, it will trigger {@link #onTaskFragmentParentInfoChanged}
      * with new Task bounds, but may not trigger {@link #onTaskFragmentInfoChanged} because there
      * can be an override bounds.
      *
-     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
-     * @param taskId    Id of the parent Task that is changed.
-     * @param parentInfo  {@link TaskFragmentParentInfo} of the parent Task.
+     * @param wct        The {@link WindowContainerTransaction} to make any changes with if needed.
+     * @param taskId     Id of the parent Task that is changed.
+     * @param parentInfo {@link TaskFragmentParentInfo} of the parent Task.
      */
     @VisibleForTesting
     @GuardedBy("mLock")
@@ -746,20 +861,20 @@
      * original Task. In this case, we need to notify the organizer so that it can check if the
      * Activity matches any split rule.
      *
-     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
-     * @param taskId            The Task that the activity is reparented to.
-     * @param activityIntent    The intent that the activity is original launched with.
-     * @param activityToken     If the activity belongs to the same process as the organizer, this
-     *                          will be the actual activity token; if the activity belongs to a
-     *                          different process, the server will generate a temporary token that
-     *                          the organizer can use to reparent the activity through
-     *                          {@link WindowContainerTransaction} if needed.
+     * @param wct            The {@link WindowContainerTransaction} to make any changes with if
+     *                       needed.
+     * @param taskId         The Task that the activity is reparented to.
+     * @param activityIntent The intent that the activity is original launched with.
+     * @param activityToken  If the activity belongs to the same process as the organizer, this
+     *                       will be the actual activity token; if the activity belongs to a
+     *                       different process, the server will generate a temporary token that
+     *                       the organizer can use to reparent the activity through
+     *                       {@link WindowContainerTransaction} if needed.
      */
     @VisibleForTesting
     @GuardedBy("mLock")
     void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
-            int taskId, @NonNull Intent activityIntent,
-            @NonNull IBinder activityToken) {
+            int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {
         // If the activity belongs to the current app process, we treat it as a new activity
         // launch.
         final Activity activity = getActivity(activityToken);
@@ -807,14 +922,15 @@
      * Called when the {@link WindowContainerTransaction} created with
      * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
      *
-     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
-     * @param errorCallbackToken    token set in
-     *                             {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
-     * @param taskFragmentInfo  The {@link TaskFragmentInfo}. This could be {@code null} if no
-     *                          TaskFragment created.
-     * @param opType            The {@link WindowContainerTransaction.HierarchyOp} of the failed
-     *                          transaction operation.
-     * @param exception             exception from the server side.
+     * @param wct                The {@link WindowContainerTransaction} to make any changes with if
+     *                           needed.
+     * @param errorCallbackToken token set in
+     *                           {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
+     * @param taskFragmentInfo   The {@link TaskFragmentInfo}. This could be {@code null} if no
+     *                           TaskFragment created.
+     * @param opType             The {@link WindowContainerTransaction.HierarchyOp} of the failed
+     *                           transaction operation.
+     * @param exception          exception from the server side.
      */
     // Suppress GuardedBy warning because lint ask to mark this method as
     // @GuardedBy(container.mController.mLock), which is mLock itself
@@ -854,7 +970,9 @@
         }
     }
 
-    /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */
+    /**
+     * Called on receiving {@link #onTaskFragmentVanished} for cleanup.
+     */
     @GuardedBy("mLock")
     private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
@@ -881,11 +999,12 @@
     /**
      * Checks if the new added activity should be routed to a particular container. It can create a
      * new container for the activity and a new split container if necessary.
-     * @param activity      the activity that is newly added to the Task.
-     * @param isOnReparent  whether the activity is reparented to the Task instead of new launched.
-     *                      We only support to split as primary for reparented activity for now.
+     *
+     * @param activity     the activity that is newly added to the Task.
+     * @param isOnReparent whether the activity is reparented to the Task instead of new launched.
+     *                     We only support to split as primary for reparented activity for now.
      * @return {@code true} if the activity has been handled, such as placed in a TaskFragment, or
-     *         in a state that the caller shouldn't handle.
+     * in a state that the caller shouldn't handle.
      */
     @VisibleForTesting
     @GuardedBy("mLock")
@@ -918,7 +1037,7 @@
         final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
         if (!isOnReparent && taskContainer != null
                 && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
-                        != container) {
+                != container) {
             // Do not resolve if the launched activity is not the top-most container (excludes
             // the pinned and overlay container) in the Task.
             return true;
@@ -943,6 +1062,7 @@
     /**
      * Resolves the activity to a {@link TaskFragmentContainer} according to the Split-rules.
      */
+    @GuardedBy("mLock")
     boolean resolveActivityToContainerByRule(@NonNull WindowContainerTransaction wct,
             @NonNull Activity activity, @Nullable TaskFragmentContainer container,
             boolean isOnReparent) {
@@ -1027,7 +1147,7 @@
     @GuardedBy("mLock")
     @VisibleForTesting
     void placeActivityInTopContainer(@NonNull WindowContainerTransaction wct,
-            @NonNull Activity activity) {
+                                     @NonNull Activity activity) {
         if (getContainerWithActivity(activity) != null) {
             // The activity has already been put in a TaskFragment. This is likely to be done by
             // the server when the activity is started.
@@ -1077,7 +1197,7 @@
      */
     @GuardedBy("mLock")
     private void expandActivity(@NonNull WindowContainerTransaction wct,
-            @NonNull Activity activity) {
+                                @NonNull Activity activity) {
         final TaskFragmentContainer container = getContainerWithActivity(activity);
         if (shouldContainerBeExpanded(container)) {
             // Make sure that the existing container is expanded.
@@ -1089,7 +1209,9 @@
         }
     }
 
-    /** Whether the given new launched activity is in a split with a rule matched. */
+    /**
+     * Whether the given new launched activity is in a split with a rule matched.
+     */
     // Suppress GuardedBy warning because lint asks to mark this method as
     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
     @SuppressWarnings("GuardedBy")
@@ -1147,7 +1269,9 @@
         return getSplitRule(primaryActivity, launchedActivity) != null;
     }
 
-    /** Finds the activity below the given activity. */
+    /**
+     * Finds the activity below the given activity.
+     */
     @VisibleForTesting
     @Nullable
     @GuardedBy("mLock")
@@ -1198,8 +1322,8 @@
                 getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity));
         if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
                 && canReuseContainer(splitRule, splitContainer.getSplitRule(),
-                        taskProperties.getTaskMetrics(),
-                        calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
+                taskProperties.getTaskMetrics(),
+                calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
             // Can launch in the existing secondary container if the rules share the same
             // presentation.
             final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
@@ -1333,7 +1457,7 @@
      *                          prioritize to split the new activity with it if it is not
      *                          {@code null}.
      * @return the {@link TaskFragmentContainer} to start the new activity in. {@code null} if there
-     *         is no embedding rule matched.
+     * is no embedding rule matched.
      */
     @VisibleForTesting
     @Nullable
@@ -1478,7 +1602,7 @@
         final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics().getBounds();
         final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
         final int windowingMode = taskContainer
-                .getWindowingModeForSplitTaskFragment(sanitizedBounds);
+                .getWindowingModeForTaskFragment(sanitizedBounds);
         mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
                 sanitizedBounds, windowingMode);
         mPresenter.updateAnimationParams(wct, taskFragmentToken,
@@ -1495,7 +1619,7 @@
      */
     @NonNull
     private static Rect sanitizeBounds(@NonNull Rect bounds, @NonNull Intent intent,
-                                       @NonNull Rect taskBounds) {
+            @NonNull Rect taskBounds) {
         if (bounds.isEmpty()) {
             // Don't need to check if the bounds follows the task bounds.
             return bounds;
@@ -1534,11 +1658,11 @@
                 getActivityIntentMinDimensionsPair(primaryActivity, intent));
         if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
                 && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics,
-                        calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())
+                calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())
                 // TODO(b/231845476) we should always respect clearTop.
                 || !respectClearTop)
                 && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
-                        null /* secondaryActivity */, intent) != RESULT_EXPAND_FAILED_NO_TF_INFO) {
+                null /* secondaryActivity */, intent) != RESULT_EXPAND_FAILED_NO_TF_INFO) {
             // Can launch in the existing secondary container if the rules share the same
             // presentation.
             return splitContainer.getSecondaryContainer();
@@ -1563,29 +1687,15 @@
     TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
         // Check pending appeared activity first because there can be a delay for the server
         // update.
-        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
-                    .getTaskFragmentContainers();
-            for (int j = containers.size() - 1; j >= 0; j--) {
-                final TaskFragmentContainer container = containers.get(j);
-                if (container.hasPendingAppearedActivity(activityToken)) {
-                    return container;
-                }
-            }
+        TaskFragmentContainer taskFragmentContainer =
+                getContainer(container -> container.hasPendingAppearedActivity(activityToken));
+        if (taskFragmentContainer != null) {
+            return taskFragmentContainer;
         }
 
+
         // Check appeared activity if there is no such pending appeared activity.
-        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
-                    .getTaskFragmentContainers();
-            for (int j = containers.size() - 1; j >= 0; j--) {
-                final TaskFragmentContainer container = containers.get(j);
-                if (container.hasAppearedActivity(activityToken)) {
-                    return container;
-                }
-            }
-        }
-        return null;
+        return getContainer(container -> container.hasAppearedActivity(activityToken));
     }
 
     @GuardedBy("mLock")
@@ -1611,8 +1721,8 @@
 
     @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
-                                       @NonNull Activity activityInTask, int taskId,
-                                       @NonNull TaskFragmentContainer pairedPrimaryContainer) {
+            @NonNull Activity activityInTask, int taskId,
+            @NonNull TaskFragmentContainer pairedPrimaryContainer) {
         return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
                 activityInTask, taskId, pairedPrimaryContainer, null /* tag */,
                 null /* launchOptions */);
@@ -1622,18 +1732,18 @@
      * Creates and registers a new organized container with an optional activity that will be
      * re-parented to it in a WCT.
      *
-     * @param pendingAppearedActivity   the activity that will be reparented to the TaskFragment.
-     * @param pendingAppearedIntent     the Intent that will be started in the TaskFragment.
-     * @param activityInTask            activity in the same Task so that we can get the Task bounds
-     *                                  if needed.
-     * @param taskId                    parent Task of the new TaskFragment.
-     * @param pairedPrimaryContainer    the paired primary {@link TaskFragmentContainer}. When it is
-     *                                  set, the new container will be added right above it.
-     * @param overlayTag                The tag for the new created overlay container. It must be
-     *                                  needed if {@code isOverlay} is {@code true}. Otherwise,
-     *                                  it should be {@code null}.
-     * @param launchOptions             The launch options bundle to create a container. Must be
-     *                                  specified for overlay container.
+     * @param pendingAppearedActivity the activity that will be reparented to the TaskFragment.
+     * @param pendingAppearedIntent   the Intent that will be started in the TaskFragment.
+     * @param activityInTask          activity in the same Task so that we can get the Task bounds
+     *                                if needed.
+     * @param taskId                  parent Task of the new TaskFragment.
+     * @param pairedPrimaryContainer  the paired primary {@link TaskFragmentContainer}. When it is
+     *                                set, the new container will be added right above it.
+     * @param overlayTag              The tag for the new created overlay container. It must be
+     *                                needed if {@code isOverlay} is {@code true}. Otherwise,
+     *                                it should be {@code null}.
+     * @param launchOptions           The launch options bundle to create a container. Must be
+     *                                specified for overlay container.
      */
     @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
@@ -1674,7 +1784,9 @@
         primaryContainer.getTaskContainer().addSplitContainer(splitContainer);
     }
 
-    /** Cleanups all the dependencies when the TaskFragment is entering PIP. */
+    /**
+     * Cleanups all the dependencies when the TaskFragment is entering PIP.
+     */
     @GuardedBy("mLock")
     private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct,
             @NonNull TaskFragmentContainer container) {
@@ -1833,16 +1945,27 @@
     @SuppressWarnings("GuardedBy")
     @GuardedBy("mLock")
     void updateOverlayContainer(@NonNull WindowContainerTransaction wct,
-                                @NonNull TaskFragmentContainer container) {
+            @NonNull TaskFragmentContainer container) {
         final TaskContainer taskContainer = container.getTaskContainer();
         // Dismiss the overlay container if it's the only container in the task and there's no
         // direct activity in the parent task.
         if (taskContainer.getTaskFragmentContainers().size() == 1
                 && !taskContainer.hasDirectActivity()) {
             container.finish(false /* shouldFinishDependent */, mPresenter, wct, this);
+            return;
         }
 
-        // TODO(b/295805054): Add the logic to update overlay container
+        if (mActivityStackAttributesCalculator != null) {
+            final ActivityStackAttributesCalculatorParams params =
+                    new ActivityStackAttributesCalculatorParams(
+                            mPresenter.createParentContainerInfoFromTaskProperties(
+                                    taskContainer.getTaskProperties()),
+                            container.getOverlayTag(),
+                            container.getLaunchOptions());
+            final ActivityStackAttributes attributes = mActivityStackAttributesCalculator
+                    .apply(params);
+            mPresenter.applyActivityStackAttributes(wct, container, attributes);
+        }
     }
 
     /**
@@ -1851,11 +1974,10 @@
      * are {@code null}, the {@link SplitAttributes} will be calculated with
      * {@link SplitPresenter#computeSplitAttributes}.
      *
-     * @param splitContainer The {@link SplitContainer} to update
+     * @param splitContainer  The {@link SplitContainer} to update
      * @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}.
      *                        Otherwise, use the value calculated by
      *                        {@link SplitPresenter#computeSplitAttributes}
-     *
      * @return {@code true} if the update succeed. Otherwise, returns {@code false}.
      */
     @VisibleForTesting
@@ -1890,7 +2012,9 @@
         return true;
     }
 
-    /** Whether the given split is the topmost split in the Task. */
+    /**
+     * Whether the given split is the topmost split in the Task.
+     */
     private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) {
         final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer()
                 .getTaskContainer().getSplitContainers();
@@ -1997,7 +2121,9 @@
         return true;
     }
 
-    /** Whether or not to allow activity in this container to launch placeholder. */
+    /**
+     * Whether or not to allow activity in this container to launch placeholder.
+     */
     @GuardedBy("mLock")
     private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
         final TaskFragmentContainer topContainer = container.getTaskContainer()
@@ -2031,8 +2157,9 @@
     /**
      * Gets the activity options for starting the placeholder activity. In case the placeholder is
      * launched when the Task is in the background, we don't want to bring the Task to the front.
-     * @param primaryActivity   the primary activity to launch the placeholder from.
-     * @param isOnCreated       whether this happens during the primary activity onCreated.
+     *
+     * @param primaryActivity the primary activity to launch the placeholder from.
+     * @param isOnCreated     whether this happens during the primary activity onCreated.
      */
     @VisibleForTesting
     @GuardedBy("mLock")
@@ -2104,7 +2231,16 @@
     @VisibleForTesting
     @GuardedBy("mLock")
     void updateCallbackIfNecessary() {
-        if (mEmbeddingCallback == null || !readyToReportToClient()) {
+        updateSplitInfoCallbackIfNecessary();
+        updateActivityStackCallbackIfNecessary();
+    }
+
+    /**
+     * Notifies callbacks about changes to split states if necessary.
+     */
+    @GuardedBy("mLock")
+    private void updateSplitInfoCallbackIfNecessary() {
+        if (!readyToReportToClient() || mSplitInfoCallback == null) {
             return;
         }
         final List<SplitInfo> currentSplitStates = getActiveSplitStatesIfStable();
@@ -2113,7 +2249,32 @@
         }
         mLastReportedSplitStates.clear();
         mLastReportedSplitStates.addAll(currentSplitStates);
-        mEmbeddingCallback.accept(currentSplitStates);
+        mSplitInfoCallback.accept(currentSplitStates);
+    }
+
+    /**
+     * Notifies callbacks about changes to {@link ActivityStack} states if necessary.
+     */
+    @GuardedBy("mLock")
+    private void updateActivityStackCallbackIfNecessary() {
+        if (!readyToReportToClient() || mActivityStackCallbacks.isEmpty()) {
+            return;
+        }
+        final List<ActivityStack> currentActivityStacks = getActivityStacksIfStable();
+        if (currentActivityStacks == null
+                || mLastReportedActivityStacks.equals(currentActivityStacks)) {
+            return;
+        }
+        mLastReportedActivityStacks.clear();
+        mLastReportedActivityStacks.addAll(currentActivityStacks);
+        // Copy the map in case a callback is removed during the for-loop.
+        final ArrayMap<Consumer<List<ActivityStack>>, Executor> callbacks =
+                new ArrayMap<>(mActivityStackCallbacks);
+        for (int i = callbacks.size() - 1; i >= 0; --i) {
+            final Executor executor = callbacks.valueAt(i);
+            final Consumer<List<ActivityStack>> callback = callbacks.keyAt(i);
+            executor.execute(() -> callback.accept(currentActivityStacks));
+        }
     }
 
     /**
@@ -2138,6 +2299,27 @@
     }
 
     /**
+     * Returns a list of currently active {@link ActivityStack activityStacks}.
+     *
+     * @return a list of {@link ActivityStack activityStacks} if all the containers are in
+     * a stable state, or {@code null} otherwise.
+     */
+    @GuardedBy("mLock")
+    @Nullable
+    private List<ActivityStack> getActivityStacksIfStable() {
+        final List<ActivityStack> activityStacks = new ArrayList<>();
+        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+            final List<ActivityStack> taskActivityStacks =
+                    mTaskContainers.valueAt(i).getActivityStacksIfStable();
+            if (taskActivityStacks == null) {
+                return null;
+            }
+            activityStacks.addAll(taskActivityStacks);
+        }
+        return activityStacks;
+    }
+
+    /**
      * Whether we can now report the split states to the client.
      */
     @GuardedBy("mLock")
@@ -2207,11 +2389,18 @@
     @Nullable
     @GuardedBy("mLock")
     TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
+        return getContainer(container -> fragmentToken.equals(container.getTaskFragmentToken()));
+    }
+
+    @Nullable
+    @GuardedBy("mLock")
+    TaskFragmentContainer getContainer(@NonNull Predicate<TaskFragmentContainer> predicate) {
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
             final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
                     .getTaskFragmentContainers();
-            for (TaskFragmentContainer container : containers) {
-                if (container.getTaskFragmentToken().equals(fragmentToken)) {
+            for (int j = containers.size() - 1; j >= 0; j--) {
+                final TaskFragmentContainer container = containers.get(j);
+                if (predicate.test(container)) {
                     return container;
                 }
             }
@@ -2304,6 +2493,7 @@
      * container. There is a case when primary containers for placeholders should be retained
      * despite the rule configuration to finish primary with secondary - if they are marked as
      * 'sticky' and the placeholder was finished when fully overlapping the primary container.
+     *
      * @return {@code true} if the associated container should be retained (and not be finished).
      */
     // Suppress GuardedBy warning because lint ask to mark this method as
@@ -2388,8 +2578,9 @@
         // If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
         // specified by Intent, expand the overlay container to fill the parent task instead.
         final ActivityStackAttributesCalculatorParams params =
-                new ActivityStackAttributesCalculatorParams(mPresenter.toParentContainerInfo(
-                        mPresenter.getTaskProperties(launchActivity)), overlayTag, options);
+                new ActivityStackAttributesCalculatorParams(
+                        mPresenter.createParentContainerInfoFromTaskProperties(
+                                mPresenter.getTaskProperties(launchActivity)), overlayTag, options);
         // Fallback to expand the bounds if there's no activityStackAttributes calculator.
         final Rect relativeBounds = mActivityStackAttributesCalculator != null
                 ? new Rect(mActivityStackAttributesCalculator.apply(params).getRelativeBounds())
@@ -2407,26 +2598,31 @@
                         && taskId == overlayContainer.getTaskId()) {
                     // If there's an overlay container with different tag shown in the same
                     // task, dismiss the existing overlay container.
-                    overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
-                            wct, SplitController.this);
+                    mPresenter.cleanupContainer(wct, overlayContainer,
+                            false /* shouldFinishDependant */);
                 }
                 if (overlayTag.equals(overlayContainer.getOverlayTag())
                         && taskId != overlayContainer.getTaskId()) {
                     // If there's an overlay container with same tag in a different task,
                     // dismiss the overlay container since the tag must be unique per process.
-                    overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
-                            wct, SplitController.this);
+                    mPresenter.cleanupContainer(wct, overlayContainer,
+                            false /* shouldFinishDependant */);
                 }
                 if (overlayTag.equals(overlayContainer.getOverlayTag())
                         && taskId == overlayContainer.getTaskId()) {
                     // If there's an overlay container with the same tag and task ID, we treat
                     // the OverlayCreateParams as the update to the container.
-                    final Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties()
-                            .getTaskMetrics().getBounds();
                     final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+                    final TaskContainer taskContainer = overlayContainer.getTaskContainer();
+                    final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics()
+                            .getBounds();
                     final Rect sanitizedBounds = sanitizeBounds(relativeBounds, intent, taskBounds);
+
                     mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
-                    mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken,
+                    final int windowingMode = taskContainer
+                            .getWindowingModeForTaskFragment(sanitizedBounds);
+                    mPresenter.updateWindowingMode(wct, overlayToken, windowingMode);
+                    mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayContainer,
                             !sanitizedBounds.isEmpty());
                     // We can just return the updated overlay container and don't need to
                     // check other condition since we only have one OverlayCreateParams, and
@@ -2539,7 +2735,9 @@
         }
     }
 
-    /** Executor that posts on the main application thread. */
+    /**
+     * Executor that posts on the main application thread.
+     */
     private static class MainThreadExecutor implements Executor {
         private final Handler mHandler = new Handler(Looper.getMainLooper());
 
@@ -2687,7 +2885,9 @@
                 && calculatedSplitAttributes.equals(containerSplitAttributes);
     }
 
-    /** Whether the two rules have the same presentation. */
+    /**
+     * Whether the two rules have the same presentation.
+     */
     @VisibleForTesting
     static boolean areRulesSamePresentation(@NonNull SplitPairRule rule1,
             @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index acfd8e4..543570c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -16,6 +16,8 @@
 
 package androidx.window.extensions.embedding;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.pm.PackageManager.MATCH_ALL;
 
 import android.app.Activity;
@@ -187,7 +189,7 @@
         final Rect secondaryRelBounds = getRelBoundsForPosition(POSITION_END, taskProperties,
                 splitAttributes);
         final int windowingMode = mController.getTaskContainer(taskId)
-                .getWindowingModeForSplitTaskFragment(secondaryRelBounds);
+                .getWindowingModeForTaskFragment(secondaryRelBounds);
         createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
                 primaryActivity.getActivityToken(), secondaryRelBounds, windowingMode);
         updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes);
@@ -259,7 +261,7 @@
         if (container == null || container == containerToAvoid) {
             container = mController.newContainer(activity, taskId);
             final int windowingMode = mController.getTaskContainer(taskId)
-                    .getWindowingModeForSplitTaskFragment(relBounds);
+                    .getWindowingModeForTaskFragment(relBounds);
             final IBinder reparentActivityToken = activity.getActivityToken();
             createTaskFragment(wct, container.getTaskFragmentToken(), reparentActivityToken,
                     relBounds, windowingMode, reparentActivityToken);
@@ -268,7 +270,7 @@
         } else {
             resizeTaskFragmentIfRegistered(wct, container, relBounds);
             final int windowingMode = mController.getTaskContainer(taskId)
-                    .getWindowingModeForSplitTaskFragment(relBounds);
+                    .getWindowingModeForTaskFragment(relBounds);
             updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
         }
         updateAnimationParams(wct, container.getTaskFragmentToken(), splitAttributes);
@@ -310,7 +312,7 @@
                 // Pass in the primary container to make sure it is added right above the primary.
                 primaryContainer);
         final TaskContainer taskContainer = mController.getTaskContainer(taskId);
-        final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
+        final int windowingMode = taskContainer.getWindowingModeForTaskFragment(
                 primaryRelBounds);
         mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
                 rule, splitAttributes);
@@ -347,6 +349,7 @@
                 && secondaryContainer.areLastRequestedBoundsEqual(null /* bounds */)
                 && !secondaryRelBounds.isEmpty();
 
+        // TODO(b/243518738): remove usages of XXXIfRegistered.
         // If the task fragments are not registered yet, the positions will be updated after they
         // are created again.
         resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRelBounds);
@@ -357,7 +360,7 @@
             // When placeholder is shown in split, we should keep the focus on the primary.
             wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
         }
-        final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
+        final int windowingMode = taskContainer.getWindowingModeForTaskFragment(
                 primaryRelBounds);
         updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode);
         updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
@@ -398,13 +401,13 @@
      * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}
      */
     void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct,
-                                           @NonNull TaskFragmentContainer taskFragmentContainer,
+                                           @NonNull TaskFragmentContainer container,
                                            boolean isolatedNavigationEnabled) {
-        if (taskFragmentContainer.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
+        if (container.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
             return;
         }
-        taskFragmentContainer.setIsolatedNavigationEnabled(isolatedNavigationEnabled);
-        setTaskFragmentIsolatedNavigation(wct, taskFragmentContainer.getTaskFragmentToken(),
+        container.setIsolatedNavigationEnabled(isolatedNavigationEnabled);
+        setTaskFragmentIsolatedNavigation(wct, container.getTaskFragmentToken(),
                 isolatedNavigationEnabled);
     }
 
@@ -566,6 +569,15 @@
         super.setCompanionTaskFragment(wct, primary, secondary);
     }
 
+    void applyActivityStackAttributes(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentContainer container, @NonNull ActivityStackAttributes attributes) {
+        final Rect bounds = attributes.getRelativeBounds();
+
+        resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds);
+        updateWindowingMode(wct, container.getTaskFragmentToken(),
+                bounds.isEmpty() ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_MULTI_WINDOW);
+    }
+
     /**
      * Expands the split container if the current split bounds are smaller than the Activity or
      * Intent that is added to the container.
@@ -1086,7 +1098,8 @@
     }
 
     @NonNull
-    ParentContainerInfo toParentContainerInfo(@NonNull TaskProperties taskProperties) {
+    ParentContainerInfo createParentContainerInfoFromTaskProperties(
+            @NonNull TaskProperties taskProperties) {
         final Configuration configuration = taskProperties.getConfiguration();
         final WindowLayoutInfo windowLayoutInfo = mWindowLayoutComponent
                 .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(),
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 028e75f..64ad4fa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -145,7 +145,7 @@
      *                              the pair of TaskFragments are stacked due to the limited space.
      */
     @WindowingMode
-    int getWindowingModeForSplitTaskFragment(@Nullable Rect taskFragmentBounds) {
+    int getWindowingModeForTaskFragment(@Nullable Rect taskFragmentBounds) {
         // Only set to multi-windowing mode if the pair are showing side-by-side. Otherwise, it
         // will be set to UNDEFINED which will then inherit the Task windowing mode.
         if (taskFragmentBounds == null || taskFragmentBounds.isEmpty() || isInPictureInPicture()) {
@@ -443,6 +443,26 @@
         return splitStates;
     }
 
+    // TODO(b/317358445): Makes ActivityStack and SplitInfo callback more stable.
+    /**
+     * Returns a list of currently active {@link ActivityStack activityStacks}.
+     *
+     * @return a list of {@link ActivityStack activityStacks} if all the containers are in
+     * a stable state, or {@code null} otherwise.
+     */
+    @Nullable
+    List<ActivityStack> getActivityStacksIfStable() {
+        final List<ActivityStack> activityStacks = new ArrayList<>();
+        for (TaskFragmentContainer container : mContainers) {
+            final ActivityStack activityStack = container.toActivityStackIfStable();
+            if (activityStack == null) {
+                return null;
+            }
+            activityStacks.add(activityStack);
+        }
+        return activityStacks;
+    }
+
     /** A wrapper class which contains the information of {@link TaskContainer} */
     static final class TaskProperties {
         private final int mDisplayId;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index afd554b..810bded 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -107,11 +107,11 @@
     private final String mOverlayTag;
 
     /**
-     * The launch options that was used to create this container. Must not be {@code null} for
-     * {@link #isOverlay()} container.
+     * The launch options that was used to create this container. Must not {@link Bundle#isEmpty()}
+     * for {@link #isOverlay()} container.
      */
-    @Nullable
-    private final Bundle mLaunchOptions;
+    @NonNull
+    private final Bundle mLaunchOptions = new Bundle();
 
     /** Indicates whether the container was cleaned up after the last activity was removed. */
     private boolean mIsFinished;
@@ -210,7 +210,9 @@
         if (overlayTag != null) {
             Objects.requireNonNull(launchOptions);
         }
-        mLaunchOptions = launchOptions;
+        if (launchOptions != null) {
+            mLaunchOptions.putAll(launchOptions);
+        }
 
         if (pairedPrimaryContainer != null) {
             // The TaskFragment will be positioned right above the paired container.
@@ -925,6 +927,17 @@
         return mOverlayTag;
     }
 
+    /**
+     * Returns the options that was used to launch this {@link TaskFragmentContainer}.
+     * {@link Bundle#isEmpty()} means there's no launch option for this container.
+     * <p>
+     * Note that WM Jetpack owns the logic. The WM Extension library must not modify this object.
+     */
+    @NonNull
+    Bundle getLaunchOptions() {
+        return mLaunchOptions;
+    }
+
     @Override
     public String toString() {
         return toString(true /* includeContainersToFinishOnExit */);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 678bdef..5ef6a52 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -16,6 +16,7 @@
 
 package androidx.window.extensions.embedding;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
@@ -287,10 +288,10 @@
         createOrUpdateOverlayTaskFragmentIfNeeded("test");
 
         verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
-        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+        verify(mSplitPresenter).updateWindowingMode(mTransaction, overlayToken,
+                WINDOWING_MODE_UNDEFINED);
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayContainer,
                 false);
-        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
-                .containsExactly(overlayContainer);
     }
 
     @Test
@@ -315,8 +316,10 @@
         createOrUpdateOverlayTaskFragmentIfNeeded("test");
 
         verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
-        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
-                false);
+        verify(mSplitPresenter).updateWindowingMode(mTransaction,
+                overlayToken, WINDOWING_MODE_UNDEFINED);
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction,
+                overlayContainer, false);
         assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
                 .containsExactly(overlayContainer);
     }
@@ -425,6 +428,50 @@
                 .that(taskContainer.getTaskFragmentContainers()).isEmpty();
     }
 
+    @Test
+    public void testUpdateActivityStackAttributes_nullParams_throwException() {
+        assertThrows(NullPointerException.class, () ->
+                mSplitController.updateActivityStackAttributes(null,
+                        new ActivityStackAttributes.Builder().build()));
+
+        assertThrows(NullPointerException.class, () ->
+                mSplitController.updateActivityStackAttributes(new Binder(), null));
+
+        verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+    }
+
+    @Test
+    public void testUpdateActivityStackAttributes_nullContainer_earlyReturn() {
+        final TaskFragmentContainer container = mSplitController.newContainer(mActivity,
+                mActivity.getTaskId());
+        mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
+                new ActivityStackAttributes.Builder().build());
+
+        verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+    }
+
+    @Test
+    public void testUpdateActivityStackAttributes_notOverlay_earlyReturn() {
+        final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+
+        mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
+                new ActivityStackAttributes.Builder().build());
+
+        verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+    }
+
+    @Test
+    public void testUpdateActivityStackAttributes() {
+        final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test");
+        doNothing().when(mSplitPresenter).applyActivityStackAttributes(any(), any(), any());
+        final ActivityStackAttributes attrs = new ActivityStackAttributes.Builder().build();
+        final IBinder token = container.getTaskFragmentToken();
+
+        mSplitController.updateActivityStackAttributes(token, attrs);
+
+        verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs));
+    }
+
     /**
      * A simplified version of {@link SplitController.ActivityStartMonitor
      * #createOrUpdateOverlayTaskFragmentIfNeeded}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index bab4e91..b60943a 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -354,7 +354,7 @@
         bundle.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
                 container.getTaskFragmentToken());
         monitor.mCurrentIntent = intent;
-        doReturn(container).when(mSplitController).getContainer(any());
+        doReturn(container).when(mSplitController).getContainer(any(IBinder.class));
 
         monitor.onStartActivityResult(START_CANCELED, bundle);
         assertNull(container.getPendingAppearedIntent());
@@ -1642,7 +1642,7 @@
         // We need to set those in case we are not respecting clear top.
         // TODO(b/231845476) we should always respect clearTop.
         final int windowingMode = mSplitController.getTaskContainer(primaryContainer.getTaskId())
-                .getWindowingModeForSplitTaskFragment(TASK_BOUNDS);
+                .getWindowingModeForTaskFragment(TASK_BOUNDS);
         primaryContainer.setLastRequestedWindowingMode(windowingMode);
         secondaryContainer.setLastRequestedWindowingMode(windowingMode);
         primaryContainer.setLastRequestedBounds(getSplitBounds(true /* isPrimary */));
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 7b77235..a5995a3 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -75,7 +75,7 @@
         final Configuration configuration = new Configuration();
 
         assertEquals(WINDOWING_MODE_MULTI_WINDOW,
-                taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
+                taskContainer.getWindowingModeForTaskFragment(splitBounds));
 
         configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
@@ -83,7 +83,7 @@
                 null /* decorSurface */));
 
         assertEquals(WINDOWING_MODE_MULTI_WINDOW,
-                taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
+                taskContainer.getWindowingModeForTaskFragment(splitBounds));
 
         configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
@@ -91,12 +91,12 @@
                 null /* decorSurface */));
 
         assertEquals(WINDOWING_MODE_FREEFORM,
-                taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
+                taskContainer.getWindowingModeForTaskFragment(splitBounds));
 
         // Empty bounds means the split pair are stacked, so it should be UNDEFINED which will then
         // inherit the Task windowing mode
         assertEquals(WINDOWING_MODE_UNDEFINED,
-                taskContainer.getWindowingModeForSplitTaskFragment(new Rect()));
+                taskContainer.getWindowingModeForTaskFragment(new Rect()));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 662a5c4..a76bd26 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -95,7 +95,6 @@
     private int mMinimumFlyoutWidthLargeScreen;
 
     private PointF mRestingStackPosition;
-    private int[] mPaddings = new int[4];
 
     private boolean mShowingInBubbleBar;
     private final Point mBubbleBarPosition = new Point();
@@ -344,46 +343,44 @@
         final int pointerTotalHeight = getPointerSize();
         final int expandedViewLargeScreenInsetFurthestEdge =
                 getExpandedViewLargeScreenInsetFurthestEdge(isOverflow);
+        int[] paddings = new int[4];
         if (mDeviceConfig.isLargeScreen()) {
             // Note:
             // If we're in portrait OR if we're a small tablet, then the two insets values will
             // be equal. If we're landscape and a large tablet, the two values will be different.
             // [left, top, right, bottom]
-            mPaddings[0] = onLeft
+            paddings[0] = onLeft
                     ? mExpandedViewLargeScreenInsetClosestEdge - pointerTotalHeight
                     : expandedViewLargeScreenInsetFurthestEdge;
-            mPaddings[1] = 0;
-            mPaddings[2] = onLeft
+            paddings[1] = 0;
+            paddings[2] = onLeft
                     ? expandedViewLargeScreenInsetFurthestEdge
                     : mExpandedViewLargeScreenInsetClosestEdge - pointerTotalHeight;
             // Overflow doesn't show manage button / get padding from it so add padding here
-            mPaddings[3] = isOverflow ? mExpandedViewPadding : 0;
-            return mPaddings;
+            paddings[3] = isOverflow ? mExpandedViewPadding : 0;
+            return paddings;
         } else {
             int leftPadding = mInsets.left + mExpandedViewPadding;
             int rightPadding = mInsets.right + mExpandedViewPadding;
-            final float expandedViewWidth = isOverflow
-                    ? mOverflowWidth
-                    : mExpandedViewLargeScreenWidth;
             if (showBubblesVertically()) {
                 if (!onLeft) {
                     rightPadding += mBubbleSize - pointerTotalHeight;
                     leftPadding += isOverflow
-                            ? (mPositionRect.width() - rightPadding - expandedViewWidth)
+                            ? (mPositionRect.width() - rightPadding - mOverflowWidth)
                             : 0;
                 } else {
                     leftPadding += mBubbleSize - pointerTotalHeight;
                     rightPadding += isOverflow
-                            ? (mPositionRect.width() - leftPadding - expandedViewWidth)
+                            ? (mPositionRect.width() - leftPadding - mOverflowWidth)
                             : 0;
                 }
             }
             // [left, top, right, bottom]
-            mPaddings[0] = leftPadding;
-            mPaddings[1] = showBubblesVertically() ? 0 : mPointerMargin;
-            mPaddings[2] = rightPadding;
-            mPaddings[3] = 0;
-            return mPaddings;
+            paddings[0] = leftPadding;
+            paddings[1] = showBubblesVertically() ? 0 : mPointerMargin;
+            paddings[2] = rightPadding;
+            paddings[3] = 0;
+            return paddings;
         }
     }
 
@@ -395,7 +392,7 @@
     }
 
     /** Gets the y position of the expanded view if it was top-aligned. */
-    public float getExpandedViewYTopAligned() {
+    public int getExpandedViewYTopAligned() {
         final int top = getAvailableRect().top;
         if (showBubblesVertically()) {
             return top - mPointerWidth + mExpandedViewPadding;
@@ -413,7 +410,7 @@
             return getExpandedViewHeightForLargeScreen();
         }
         // Subtract top insets because availableRect.height would account for that
-        int expandedContainerY = (int) getExpandedViewYTopAligned() - getInsets().top;
+        int expandedContainerY = getExpandedViewYTopAligned() - getInsets().top;
         int paddingTop = showBubblesVertically()
                 ? 0
                 : mPointerHeight;
@@ -474,11 +471,11 @@
     public float getExpandedViewY(BubbleViewProvider bubble, float bubblePosition) {
         boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
         float expandedViewHeight = getExpandedViewHeight(bubble);
-        float topAlignment = getExpandedViewYTopAligned();
+        int topAlignment = getExpandedViewYTopAligned();
         int manageButtonHeight =
                 isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
 
-        // On largescreen portrait bubbles are bottom aligned.
+        // On large screen portrait bubbles are bottom aligned.
         if (areBubblesBottomAligned() && expandedViewHeight == MAX_HEIGHT) {
             return mPositionRect.bottom - manageButtonHeight
                     - getExpandedViewHeightForLargeScreen() - mPointerWidth;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 02af2d0..7798aa7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -420,7 +420,7 @@
             bubbleView.setTranslationY(y);
         }
 
-        final float expandedY = mPositioner.getExpandedViewYTopAligned();
+        final int expandedY = mPositioner.getExpandedViewYTopAligned();
         final boolean draggedOutEnough =
                 y > expandedY + mBubbleSizePx || y < expandedY - mBubbleSizePx;
         if (draggedOutEnough != mBubbleDraggedOutEnough) {
diff --git a/libs/incident/libincident.map.txt b/libs/incident/libincident.map.txt
index f75ccea..d8650e1 100644
--- a/libs/incident/libincident.map.txt
+++ b/libs/incident/libincident.map.txt
@@ -1,15 +1,15 @@
 LIBINCIDENT {
     global:
-        AIncidentReportArgs_init; # systemapi # introduced=30
-        AIncidentReportArgs_clone; # systemapi # introduced=30
-        AIncidentReportArgs_delete; # systemapi # introduced=30
-        AIncidentReportArgs_setAll; # systemapi # introduced=30
-        AIncidentReportArgs_setPrivacyPolicy; # systemapi # introduced=30
-        AIncidentReportArgs_addSection; # systemapi # introduced=30
-        AIncidentReportArgs_setReceiverPackage; # systemapi # introduced=30
-        AIncidentReportArgs_setReceiverClass; # systemapi # introduced=30
-        AIncidentReportArgs_addHeader; # systemapi # introduced=30
-        AIncidentReportArgs_takeReport; # systemapi # introduced=30
+        AIncidentReportArgs_init; # systemapi introduced=30
+        AIncidentReportArgs_clone; # systemapi introduced=30
+        AIncidentReportArgs_delete; # systemapi introduced=30
+        AIncidentReportArgs_setAll; # systemapi introduced=30
+        AIncidentReportArgs_setPrivacyPolicy; # systemapi introduced=30
+        AIncidentReportArgs_addSection; # systemapi introduced=30
+        AIncidentReportArgs_setReceiverPackage; # systemapi introduced=30
+        AIncidentReportArgs_setReceiverClass; # systemapi introduced=30
+        AIncidentReportArgs_addHeader; # systemapi introduced=30
+        AIncidentReportArgs_takeReport; # systemapi introduced=30
     local:
         *;
 };
diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
index 3b15632..ce1004c 100644
--- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
+++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
@@ -95,12 +95,17 @@
     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
     public void setAudioTrack_callsAudioServiceStart() throws Exception {
         final AudioTrack track = createAudioTrack();
+        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
 
-        mLcc.addMediaCodec(createAndConfigureMediaCodec());
-        mLcc.setAudioTrack(track);
+        try {
+            mLcc.addMediaCodec(mediaCodec);
+            mLcc.setAudioTrack(track);
 
-        verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
-                anyList());
+            verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
+                    anyList());
+        } finally {
+            mediaCodec.release();
+        }
     }
 
     @Test
@@ -108,10 +113,15 @@
     public void getLoudnessCodecParams_callsAudioServiceGetLoudness() throws Exception {
         when(mAudioService.getLoudnessParams(anyInt(), any())).thenReturn(new PersistableBundle());
         final AudioTrack track = createAudioTrack();
+        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
 
-        mLcc.getLoudnessCodecParams(track, createAndConfigureMediaCodec());
+        try {
+            mLcc.getLoudnessCodecParams(track, mediaCodec);
 
-        verify(mAudioService).getLoudnessParams(eq(track.getPlayerIId()), any());
+            verify(mAudioService).getLoudnessParams(eq(track.getPlayerIId()), any());
+        } finally {
+            mediaCodec.release();
+        }
     }
 
     @Test
@@ -120,10 +130,14 @@
         final AudioTrack track = createAudioTrack();
         final MediaCodec mediaCodec = createAndConfigureMediaCodec();
 
-        mLcc.addMediaCodec(mediaCodec);
-        mLcc.setAudioTrack(track);
+        try {
+            mLcc.addMediaCodec(mediaCodec);
+            mLcc.setAudioTrack(track);
 
-        verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+            verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+        } finally {
+            mediaCodec.release();
+        }
     }
 
     @Test
@@ -132,24 +146,33 @@
         final AudioTrack track = createAudioTrack();
         final MediaCodec mediaCodec = createAndConfigureMediaCodec();
 
-        mLcc.addMediaCodec(mediaCodec);
-        mLcc.setAudioTrack(track);
-        mLcc.setAudioTrack(track);
+        try {
+            mLcc.addMediaCodec(mediaCodec);
+            mLcc.setAudioTrack(track);
+            mLcc.setAudioTrack(track);
 
-        verify(mAudioService, times(1)).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
-                anyList());
+            verify(mAudioService, times(1)).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
+                    anyList());
+        } finally {
+            mediaCodec.release();
+        }
     }
 
     @Test
     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
     public void setTrackNull_stopCodecUpdates() throws Exception {
         final AudioTrack track = createAudioTrack();
+        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
 
-        mLcc.addMediaCodec(createAndConfigureMediaCodec());
-        mLcc.setAudioTrack(track);
+        try {
+            mLcc.addMediaCodec(mediaCodec);
+            mLcc.setAudioTrack(track);
 
-        mLcc.setAudioTrack(null);  // stops updates
-        verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
+            mLcc.setAudioTrack(null);  // stops updates
+            verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
+        } finally {
+            mediaCodec.release();
+        }
     }
 
     @Test
@@ -157,27 +180,37 @@
     public void addMediaCodecTwice_triggersIAE() throws Exception {
         final MediaCodec mediaCodec = createAndConfigureMediaCodec();
 
-        mLcc.addMediaCodec(mediaCodec);
+        try {
+            mLcc.addMediaCodec(mediaCodec);
 
-        assertThrows(IllegalArgumentException.class, () -> mLcc.addMediaCodec(mediaCodec));
+            assertThrows(IllegalArgumentException.class, () -> mLcc.addMediaCodec(mediaCodec));
+        } finally {
+            mediaCodec.release();
+        }
     }
 
     @Test
     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
     public void setClearTrack_removeAllAudioServicePiidCodecs() throws Exception {
         final ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
-
         final AudioTrack track = createAudioTrack();
+        final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
+        final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
 
-        mLcc.addMediaCodec(createAndConfigureMediaCodec());
-        mLcc.setAudioTrack(track);
-        verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
-                argument.capture());
-        assertEquals(argument.getValue().size(), 1);
+        try {
+            mLcc.addMediaCodec(mediaCodec1);
+            mLcc.setAudioTrack(track);
+            verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
+                    argument.capture());
+            assertEquals(argument.getValue().size(), 1);
 
-        mLcc.addMediaCodec(createAndConfigureMediaCodec());
-        mLcc.setAudioTrack(null);
-        verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
+            mLcc.addMediaCodec(mediaCodec2);
+            mLcc.setAudioTrack(null);
+            verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
+        } finally {
+            mediaCodec1.release();
+            mediaCodec2.release();
+        }
     }
 
     @Test
@@ -186,24 +219,35 @@
         final AudioTrack track = createAudioTrack();
         final MediaCodec mediaCodec = createAndConfigureMediaCodec();
 
-        mLcc.addMediaCodec(mediaCodec);
-        mLcc.setAudioTrack(track);
-        mLcc.removeMediaCodec(mediaCodec);
+        try {
+            mLcc.addMediaCodec(mediaCodec);
+            mLcc.setAudioTrack(track);
+            mLcc.removeMediaCodec(mediaCodec);
 
-        verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
+            verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
+        } finally {
+            mediaCodec.release();
+        }
     }
 
     @Test
     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
     public void addMediaCodecAfterSetTrack_callsAudioServiceAdd() throws Exception {
         final AudioTrack track = createAudioTrack();
+        final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
+        final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
 
-        mLcc.addMediaCodec(createAndConfigureMediaCodec());
-        mLcc.setAudioTrack(track);
-        verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+        try {
+            mLcc.addMediaCodec(mediaCodec1);
+            mLcc.setAudioTrack(track);
+            verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
 
-        mLcc.addMediaCodec(createAndConfigureMediaCodec());
-        verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), anyInt(), any());
+            mLcc.addMediaCodec(mediaCodec2);
+            verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), anyInt(), any());
+        } finally {
+            mediaCodec1.release();
+            mediaCodec2.release();
+        }
     }
 
     @Test
@@ -212,25 +256,36 @@
         final AudioTrack track = createAudioTrack();
         final MediaCodec mediaCodec = createAndConfigureMediaCodec();
 
-        mLcc.addMediaCodec(mediaCodec);
-        mLcc.setAudioTrack(track);
-        verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+        try {
+            mLcc.addMediaCodec(mediaCodec);
+            mLcc.setAudioTrack(track);
+            verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
 
-        mLcc.removeMediaCodec(mediaCodec);
-        verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
+            mLcc.removeMediaCodec(mediaCodec);
+            verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
+        } finally {
+            mediaCodec.release();
+        }
     }
 
     @Test
     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
     public void removeWrongMediaCodecAfterSetTrack_triggersIAE() throws Exception {
         final AudioTrack track = createAudioTrack();
+        final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
+        final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
 
-        mLcc.addMediaCodec(createAndConfigureMediaCodec());
-        mLcc.setAudioTrack(track);
-        verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+        try {
+            mLcc.addMediaCodec(mediaCodec1);
+            mLcc.setAudioTrack(track);
+            verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
 
-        assertThrows(IllegalArgumentException.class,
-                () -> mLcc.removeMediaCodec(createAndConfigureMediaCodec()));
+            assertThrows(IllegalArgumentException.class,
+                    () -> mLcc.removeMediaCodec(mediaCodec2));
+        } finally {
+            mediaCodec1.release();
+            mediaCodec2.release();
+        }
     }
 
     private static AudioTrack createAudioTrack() {
@@ -250,19 +305,21 @@
 
         MediaExtractor extractor;
         extractor = new MediaExtractor();
-        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+        try {
+            extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
                 testFd.getLength());
-        testFd.close();
+            assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
+            MediaFormat format = extractor.getTrackFormat(0);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            assertTrue("not an audio file", mime.startsWith(TEST_MEDIA_AUDIO_CODEC_PREFIX));
+            final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mime);
 
-        assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
-        MediaFormat format = extractor.getTrackFormat(0);
-        String mime = format.getString(MediaFormat.KEY_MIME);
-        assertTrue("not an audio file", mime.startsWith(TEST_MEDIA_AUDIO_CODEC_PREFIX));
-        final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mime);
-
-        Log.v(TAG, "configuring with " + format);
-        mediaCodec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
-
-        return mediaCodec;
+            Log.v(TAG, "configuring with " + format);
+            mediaCodec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+            return mediaCodec;
+        } finally {
+            testFd.close();
+            extractor.release();
+        }
     }
 }
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 9f2a9ac..9605108 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -1,9 +1,9 @@
 LIBANDROID {
   global:
-    AActivityManager_addUidImportanceListener; # systemapi # introduced=31
-    AActivityManager_removeUidImportanceListener; # systemapi # introduced=31
-    AActivityManager_isUidActive; # systemapi # introduced=31
-    AActivityManager_getUidImportance; # systemapi # introduced=31
+    AActivityManager_addUidImportanceListener; # systemapi introduced=31
+    AActivityManager_removeUidImportanceListener; # systemapi introduced=31
+    AActivityManager_isUidActive; # systemapi introduced=31
+    AActivityManager_getUidImportance; # systemapi introduced=31
     AAssetDir_close;
     AAssetDir_getNextFileName;
     AAssetDir_rewind;
diff --git a/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml b/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml
new file mode 100644
index 0000000..9d16f32d
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- Copied from //frameworks/base/core/res/res/drawable/item_background_material.xml -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@color/autofill_light_colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <color android:color="@android:color/white"/>
+    </item>
+</ripple>
\ No newline at end of file
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
new file mode 100644
index 0000000..2f0c83b
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<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:radius="28dp" />
+            <solid android:color="@android:color/system_surface_container_high_light" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml
new file mode 100644
index 0000000..39f49ca
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<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:radius="28dp" />
+            <solid android:color="@android:color/system_surface_container_high_dark" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml b/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml
new file mode 100644
index 0000000..e4e9f7a
--- /dev/null
+++ b/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml
@@ -0,0 +1,37 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:id="@android:id/content"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/autofill.Dataset">
+    <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: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"
+        style="@style/autofill.TextAppearance"/>
+
+</RelativeLayout>
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
new file mode 100644
index 0000000..63b9f24
--- /dev/null
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ 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.
+  -->
+
+<!-- Color palette -->
+<resources>
+    <color name="autofill_light_colorPrimary">@color/primary_material_light</color>
+    <color name="autofill_light_colorAccent">@color/accent_material_light</color>
+    <color name="autofill_light_colorControlHighlight">@color/ripple_material_light</color>
+    <color name="autofill_light_colorButtonNormal">@color/button_material_light</color>
+
+    <!-- Text colors -->
+    <color name="autofill_light_textColorPrimary">@color/abc_primary_text_material_light</color>
+    <color name="autofill_light_textColorSecondary">@color/abc_secondary_text_material_light</color>
+    <color name="autofill_light_textColorHint">@color/abc_hint_foreground_material_light</color>
+    <color name="autofill_light_textColorHintInverse">@color/abc_hint_foreground_material_dark
+    </color>
+    <color name="autofill_light_textColorHighlight">@color/highlighted_text_material_light</color>
+    <color name="autofill_light_textColorLink">@color/autofill_light_colorAccent</color>
+
+    <!-- These colors are used for Remote Views. -->
+    <color name="background_dark_mode">#0E0C0B</color>
+    <color name="background">#F1F3F4</color>
+    <color name="text_primary_dark_mode">#DFDEDB</color>
+    <color name="text_primary">#202124</color>
+</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
new file mode 100644
index 0000000..67003a3
--- /dev/null
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <dimen name="autofill_view_padding">16dp</dimen>
+    <dimen name="autofill_icon_size">16dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/styles.xml b/packages/CredentialManager/res/values/styles.xml
new file mode 100644
index 0000000..4a5761a
--- /dev/null
+++ b/packages/CredentialManager/res/values/styles.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <style name="autofill.TextAppearance.Small" parent="@style/autofill.TextAppearance">
+        <item name="android:textSize">12sp</item>
+    </style>
+
+
+    <style name="autofill.Dataset" parent="">
+        <item name="android:background">@drawable/autofill_light_selectable_item_background</item>
+    </style>
+
+    <style name="autofill.TextAppearance" parent="">
+        <item name="android:textColor">@color/autofill_light_textColorPrimary</item>
+        <item name="android:textColorHint">@color/autofill_light_textColorHint</item>
+        <item name="android:textColorHighlight">@color/autofill_light_textColorHighlight</item>
+        <item name="android:textColorLink">@color/autofill_light_textColorLink</item>
+        <item name="android:textSize">14sp</item>
+    </style>
+
+    <style name="autofill.TextAppearance.Primary">
+        <item name="android:textColor">@color/autofill_light_textColorPrimary</item>
+    </style>
+</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 20d2f09..0ff1c7f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -16,6 +16,7 @@
 
 package com.android.credentialmanager.autofill
 
+import android.R
 import android.app.assist.AssistStructure
 import android.content.Context
 import android.credentials.CredentialManager
@@ -41,18 +42,19 @@
 import android.service.credentials.CredentialProviderService
 import android.util.Log
 import android.view.autofill.AutofillId
-import org.json.JSONException
 import android.widget.inline.InlinePresentationSpec
 import androidx.autofill.inline.v1.InlineSuggestionUi
 import androidx.credentials.provider.CustomCredentialEntry
 import androidx.credentials.provider.PasswordCredentialEntry
 import androidx.credentials.provider.PublicKeyCredentialEntry
 import com.android.credentialmanager.GetFlowUtils
-import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.common.ui.RemoteViewsFactory
 import com.android.credentialmanager.getflow.ProviderDisplayInfo
-import com.android.credentialmanager.model.get.ProviderInfo
 import com.android.credentialmanager.getflow.toProviderDisplayInfo
 import com.android.credentialmanager.ktx.credentialEntry
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.model.get.ProviderInfo
+import org.json.JSONException
 import org.json.JSONObject
 import java.util.concurrent.Executors
 
@@ -127,9 +129,11 @@
                     is PasswordCredentialEntry -> {
                         entryIconMap[entry.key + entry.subkey] = credentialEntry.icon
                     }
+
                     is PublicKeyCredentialEntry -> {
                         entryIconMap[entry.key + entry.subkey] = credentialEntry.icon
                     }
+
                     is CustomCredentialEntry -> {
                         entryIconMap[entry.key + entry.subkey] = credentialEntry.icon
                     }
@@ -172,11 +176,11 @@
     }
 
     private fun processProvidersForAutofillId(
-        filLRequest: FillRequest,
-        autofillId: AutofillId,
-        providerList: List<ProviderInfo>,
-        entryIconMap: Map<String, Icon>,
-        fillResponseBuilder: FillResponse.Builder
+            filLRequest: FillRequest,
+            autofillId: AutofillId,
+            providerList: List<ProviderInfo>,
+            entryIconMap: Map<String, Icon>,
+            fillResponseBuilder: FillResponse.Builder
     ): Boolean {
         if (providerList.isEmpty()) {
             return false
@@ -197,7 +201,7 @@
         var i = 0
         var datasetAdded = false
 
-        providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@ {
+        providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{
             val primaryEntry = it.sortedCredentialEntryList.first()
             val pendingIntent = primaryEntry.pendingIntent
             val fillInIntent = primaryEntry.fillInIntent
@@ -206,37 +210,48 @@
                 Log.e(TAG, "PendingIntent was missing from the entry.")
                 return@usernameLoop
             }
-            if (inlinePresentationSpecs == null || i >= maxItemCount) {
+            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
             }
-            // Create inline presentation
-            val spec: InlinePresentationSpec
-            if (i < inlinePresentationSpecsCount) {
-                spec = inlinePresentationSpecs[i]
-            } else {
-                spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
-            }
-            val sliceBuilder = InlineSuggestionUi
-                    .newContentBuilder(pendingIntent)
-                    .setTitle(primaryEntry.userName)
-            val icon: Icon
-            if (primaryEntry.icon == null) {
+            val icon: Icon = if (primaryEntry.icon == null) {
                 // The empty entry icon has non-null icon reference but null drawable reference.
                 // If the drawable reference is null, then use the default icon.
-                icon = getDefaultIcon()
+                getDefaultIcon()
             } else {
-                icon = entryIconMap[primaryEntry.entryKey + primaryEntry.entrySubkey]
+                entryIconMap[primaryEntry.entryKey + primaryEntry.entrySubkey]
                         ?: getDefaultIcon()
             }
-            sliceBuilder.setStartIcon(icon)
-            val inlinePresentation = InlinePresentation(
-                    sliceBuilder.build().slice, spec, /* pinned= */ false)
+            // Create inline presentation
+            var inlinePresentation: InlinePresentation? = null;
+            if (inlinePresentationSpecs != null) {
+                val spec: InlinePresentationSpec
+                if (i < inlinePresentationSpecsCount) {
+                    spec = inlinePresentationSpecs[i]
+                } else {
+                    spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+                }
+                val sliceBuilder = InlineSuggestionUi
+                        .newContentBuilder(pendingIntent)
+                        .setTitle(primaryEntry.userName)
+                sliceBuilder.setStartIcon(icon)
+                inlinePresentation = InlinePresentation(
+                        sliceBuilder.build().slice, spec, /* pinned= */ false)
+            }
+            val dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
+                    this, icon, primaryEntry)
             i++
 
             val dataSetBuilder = Dataset.Builder()
             val presentationBuilder = Presentations.Builder()
-                    .setInlinePresentation(inlinePresentation)
+                    .setMenuPresentation(dropdownPresentation)
+            if (inlinePresentation != null) {
+                presentationBuilder.setInlinePresentation(inlinePresentation)
+            }
 
             fillResponseBuilder.addDataset(
                     dataSetBuilder
@@ -305,7 +320,7 @@
     ): MutableMap<AutofillId, MutableList<CredentialEntryInfo>> {
         val autofillIdToCredentialEntries:
                 MutableMap<AutofillId, MutableList<CredentialEntryInfo>> = mutableMapOf()
-        credentialEntryList.forEach entryLoop@ { credentialEntry ->
+        credentialEntryList.forEach entryLoop@{ credentialEntry ->
             val autofillId: AutofillId? = credentialEntry
                     .fillInIntent
                     ?.getParcelableExtra(
@@ -323,8 +338,8 @@
     }
 
     private fun copyProviderInfo(
-        providerInfo: ProviderInfo,
-        credentialList: List<CredentialEntryInfo>
+            providerInfo: ProviderInfo,
+            credentialList: List<CredentialEntryInfo>
     ): ProviderInfo {
         return ProviderInfo(
                 providerInfo.id,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
new file mode 100644
index 0000000..4dc7f00
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.credentialmanager.common.ui
+
+import android.content.Context
+import android.content.res.Configuration
+import android.widget.RemoteViews
+import androidx.core.content.ContextCompat
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import android.graphics.drawable.Icon
+
+class RemoteViewsFactory {
+
+    companion object {
+        private const val setAdjustViewBoundsMethodName = "setAdjustViewBounds"
+        private const val setMaxHeightMethodName = "setMaxHeight"
+        private const val setBackgroundResourceMethodName = "setBackgroundResource"
+
+        fun createDropdownPresentation(
+                context: Context,
+                icon: Icon,
+                credentialEntryInfo: CredentialEntryInfo
+        ): RemoteViews {
+            val padding = context.resources.getDimensionPixelSize(com.android
+                    .credentialmanager.R.dimen.autofill_view_padding)
+            var layoutId: Int = com.android.credentialmanager.R.layout
+                    .autofill_dataset_left_with_item_tag_hint
+            val remoteViews = RemoteViews(context.packageName, layoutId)
+            setRemoteViewsPaddings(remoteViews, padding)
+            val textColorPrimary = getTextColorPrimary(isDarkMode(context), context);
+            remoteViews.setTextColor(android.R.id.text1, textColorPrimary);
+            remoteViews.setTextViewText(android.R.id.text1, credentialEntryInfo.userName)
+
+            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 = if (isDarkMode(context))
+                com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one_dark
+            else com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one
+            remoteViews.setInt(
+                    android.R.id.content, setBackgroundResourceMethodName, drawableId);
+            return remoteViews
+        }
+
+        private fun setRemoteViewsPaddings(
+                remoteViews: RemoteViews,
+                padding: Int) {
+            val halfPadding = padding / 2
+            remoteViews.setViewPadding(
+                    android.R.id.text1,
+                    halfPadding,
+                    halfPadding,
+                    halfPadding,
+                    halfPadding)
+        }
+
+        private fun isDarkMode(context: Context): Boolean {
+            val currentNightMode = context.resources.configuration.uiMode and
+                    Configuration.UI_MODE_NIGHT_MASK
+            return currentNightMode == Configuration.UI_MODE_NIGHT_YES
+        }
+
+        private fun getTextColorPrimary(darkMode: Boolean, context: Context): Int {
+            return if (darkMode) ContextCompat.getColor(
+                    context, com.android.credentialmanager.R.color.text_primary_dark_mode)
+            else ContextCompat.getColor(context, com.android.credentialmanager.R.color.text_primary)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
index 1ee58de..5e76801 100644
--- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.util.settings.SecureSettings
 import java.io.PrintWriter
 import javax.inject.Inject
+import dagger.Lazy
 
 /**
  * Handles active unlock settings changes.
@@ -51,6 +52,7 @@
     private val secureSettings: SecureSettings,
     private val contentResolver: ContentResolver,
     private val selectedUserInteractor: SelectedUserInteractor,
+    private val keyguardUpdateMonitor: Lazy<KeyguardUpdateMonitor>,
     dumpManager: DumpManager
 ) : Dumpable {
 
@@ -96,7 +98,6 @@
         UNDER_DISPLAY_FINGERPRINT(3),
     }
 
-    var keyguardUpdateMonitor: KeyguardUpdateMonitor? = null
     private var requestActiveUnlockOnWakeup = false
     private var requestActiveUnlockOnUnlockIntent = false
     private var requestActiveUnlockOnBioFail = false
@@ -316,7 +317,7 @@
             return false
         }
 
-        keyguardUpdateMonitor?.let {
+        keyguardUpdateMonitor.get().let {
             val anyFaceEnrolled = it.isFaceEnabledAndEnrolled
             val anyFingerprintEnrolled = it.isUnlockWithFingerprintPossible(
                     selectedUserInteractor.getSelectedUserId())
@@ -369,13 +370,13 @@
         }")
 
         pw.println("Current state:")
-        keyguardUpdateMonitor?.let {
+        keyguardUpdateMonitor.get().let {
             pw.println("   shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment=" +
                     "${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}")
             pw.println("   isFaceEnabledAndEnrolled=${it.isFaceEnabledAndEnrolled}")
             pw.println("   fpUnlockPossible=${
                 it.isUnlockWithFingerprintPossible(selectedUserInteractor.getSelectedUserId())}")
             pw.println("   udfpsEnrolled=${it.isUdfpsEnrolled}")
-        } ?: pw.println("   keyguardUpdateMonitor is uninitialized")
+        }
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9c61a8a..f3cd01f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -111,6 +111,7 @@
 import com.android.settingslib.Utils;
 import com.android.settingslib.WirelessUtils;
 import com.android.settingslib.fuelgauge.BatteryStatus;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.Dumpable;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
@@ -175,7 +176,7 @@
  * to be updated.
  */
 @SysUISingleton
-public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpable {
+public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpable, CoreStartable {
 
     private static final String TAG = "KeyguardUpdateMonitor";
     private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600;
@@ -347,6 +348,7 @@
     private final LatencyTracker mLatencyTracker;
     private final StatusBarStateController mStatusBarStateController;
     private final Executor mBackgroundExecutor;
+    private final Executor mMainExecutor;
     private final SensorPrivacyManager mSensorPrivacyManager;
     private final ActiveUnlockConfig mActiveUnlockConfig;
     private final IDreamManager mDreamManager;
@@ -354,7 +356,10 @@
     @Nullable
     private final FingerprintManager mFpm;
     @Nullable
+    private final BiometricManager mBiometricManager;
+    @Nullable
     private KeyguardFaceAuthInteractor mFaceAuthInteractor;
+    private final DevicePostureController mDevicePostureController;
     private final TaskStackChangeListeners mTaskStackChangeListeners;
     private final IActivityTaskManager mActivityTaskManager;
     private final SelectedUserInteractor mSelectedUserInteractor;
@@ -370,7 +375,7 @@
     private boolean mIsDreaming;
     private boolean mLogoutEnabled;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-    private FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider;
+    private final FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider;
 
     /**
      * Short delay before restarting fingerprint authentication after a successful try. This should
@@ -2105,6 +2110,7 @@
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
         mStrongAuthTracker = new StrongAuthTracker(context);
         mBackgroundExecutor = backgroundExecutor;
+        mMainExecutor = mainExecutor;
         mBroadcastDispatcher = broadcastDispatcher;
         mInteractionJankMonitor = interactionJankMonitor;
         mLatencyTracker = latencyTracker;
@@ -2126,7 +2132,7 @@
         mDevicePolicyManager = devicePolicyManager;
         mPackageManager = packageManager;
         mFpm = fingerprintManager;
-        mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
+        mBiometricManager = biometricManager;
         mConfigFaceAuthSupportedPosture = mContext.getResources().getInteger(
                 R.integer.config_face_auth_supported_posture);
         mFaceWakeUpTriggersConfig = faceWakeUpTriggersConfig;
@@ -2134,10 +2140,14 @@
                 mContext.getResources().getStringArray(
                         R.array.config_fingerprint_listen_on_occluding_activity_packages))
                 .collect(Collectors.toSet());
+        mDevicePostureController = devicePostureController;
         mTaskStackChangeListeners = taskStackChangeListeners;
         mActivityTaskManager = activityTaskManagerService;
         mSelectedUserInteractor = selectedUserInteractor;
 
+        mFingerprintInteractiveToAuthProvider = interactiveToAuthProvider.orElse(null);
+        mIsSystemUser = mUserManager.isSystemUser();
+
         mHandler = new Handler(mainLooper) {
             @Override
             public void handleMessage(Message msg) {
@@ -2252,6 +2262,20 @@
             }
         };
 
+        mTimeFormatChangeObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                mHandler.sendMessage(mHandler.obtainMessage(
+                        MSG_TIME_FORMAT_UPDATE,
+                        Settings.System.getString(
+                                mContext.getContentResolver(),
+                                Settings.System.TIME_12_24)));
+            }
+        };
+    }
+
+    @Override
+    public void start() {
         // Since device can't be un-provisioned, we only need to register a content observer
         // to update mDeviceProvisioned when we are...
         if (!mDeviceProvisioned) {
@@ -2297,7 +2321,7 @@
                 mHandler, UserHandle.ALL);
 
         mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
-        mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
+        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
 
         mTrustManager.registerTrustListener(this);
 
@@ -2318,8 +2342,8 @@
             mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
         }
 
-        if (biometricManager != null) {
-            biometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback);
+        if (mBiometricManager != null) {
+            mBiometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback);
         }
 
         // in case authenticators aren't registered yet at this point:
@@ -2327,7 +2351,7 @@
             @Override
             public void onAllAuthenticatorsRegistered(
                     @BiometricAuthenticator.Modality int modality) {
-                mainExecutor.execute(
+                mMainExecutor.execute(
                         () -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE));
             }
 
@@ -2335,7 +2359,7 @@
             public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
                 mHandler.obtainMessage(MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED, modality, 0)
                         .sendToTarget();
-                mainExecutor.execute(
+                mMainExecutor.execute(
                         () -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE));
             }
 
@@ -2353,12 +2377,11 @@
             }
         });
         if (mConfigFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
-            devicePostureController.addCallback(mPostureCallback);
+            mDevicePostureController.addCallback(mPostureCallback);
         }
         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
 
         mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
-        mIsSystemUser = mUserManager.isSystemUser();
         int user = mSelectedUserInteractor.getSelectedUserId(true);
         mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
         mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
@@ -2377,22 +2400,9 @@
         mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
         initializeSimState();
 
-        mTimeFormatChangeObserver = new ContentObserver(mHandler) {
-            @Override
-            public void onChange(boolean selfChange) {
-                mHandler.sendMessage(mHandler.obtainMessage(
-                        MSG_TIME_FORMAT_UPDATE,
-                        Settings.System.getString(
-                                mContext.getContentResolver(),
-                                Settings.System.TIME_12_24)));
-            }
-        };
-
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.TIME_12_24),
                 false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
-
-        mFingerprintInteractiveToAuthProvider = interactiveToAuthProvider.orElse(null);
     }
 
     private void initializeSimState() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 83d415f..ab23564 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -403,13 +403,6 @@
 
             final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
                     R.layout.biometric_prompt_layout, null, false);
-            /**
-             * View is only set visible in BiometricViewSizeBinder once PromptSize is determined
-             * that accounts for iconView size, to prevent prompt resizing being visible to the
-             * user.
-             * TODO(b/288175072): May be able to remove this once constraint layout is implemented
-             */
-            view.setVisibility(View.INVISIBLE);
             mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
                     // TODO(b/201510778): This uses the wrong timeout in some cases
                     getJankListener(view, TRANSIT,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index a7fb6f7..90e4a38 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -97,13 +97,7 @@
 
         val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
         val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
-        /**
-         * View is only set visible in BiometricViewSizeBinder once PromptSize is determined that
-         * accounts for iconView size, to prevent prompt resizing being visible to the user.
-         *
-         * TODO(b/288175072): May be able to remove this once constraint layout is implemented
-         */
-        iconView.addLottieOnCompositionLoadedListener { viewModel.setIsIconViewLoaded(true) }
+
         PromptIconViewBinder.bind(
             iconView,
             iconOverlayView,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index f340bd8..7e16d1e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -30,6 +30,7 @@
 import androidx.core.view.doOnLayout
 import androidx.core.view.isGone
 import androidx.lifecycle.lifecycleScope
+import com.android.systemui.res.R
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.ui.BiometricPromptLayout
@@ -40,8 +41,6 @@
 import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
 import com.android.systemui.biometrics.ui.viewmodel.isSmall
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.res.R
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 /** Helper for [BiometricViewBinder] to handle resize transitions. */
@@ -93,22 +92,8 @@
             // TODO(b/251476085): migrate the legacy panel controller and simplify this
             view.repeatWhenAttached {
                 var currentSize: PromptSize? = null
-
                 lifecycleScope.launch {
-                    /**
-                     * View is only set visible in BiometricViewSizeBinder once PromptSize is
-                     * determined that accounts for iconView size, to prevent prompt resizing being
-                     * visible to the user.
-                     *
-                     * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
-                     *   layout is implemented
-                     */
-                    combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
-                        (isIconViewLoaded, size) ->
-                        if (!isIconViewLoaded) {
-                            return@collect
-                        }
-
+                    viewModel.size.collect { size ->
                         // prepare for animated size transitions
                         for (v in viewsToHideWhenSmall) {
                             v.showTextOrHide(forceHide = size.isSmall)
@@ -211,9 +196,8 @@
                                     }
                                 }
                             }
+
                             currentSize = size
-                            view.visibility = View.VISIBLE
-                            viewModel.setIsIconViewLoaded(false)
                             notifyAccessibilityChanged()
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index d899827e..6d0a58e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -192,28 +192,6 @@
     val iconViewModel: PromptIconViewModel =
         PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
 
-    private val _isIconViewLoaded = MutableStateFlow(false)
-
-    /**
-     * For prompts with an iconView, false until the prompt's iconView animation has been loaded in
-     * the view, otherwise true by default. Used for BiometricViewSizeBinder to wait for the icon
-     * asset to be loaded before determining the prompt size.
-     */
-    val isIconViewLoaded: Flow<Boolean> =
-        combine(credentialKind, _isIconViewLoaded.asStateFlow()) { credentialKind, isIconViewLoaded
-            ->
-            if (credentialKind is PromptKind.Biometric) {
-                isIconViewLoaded
-            } else {
-                true
-            }
-        }
-
-    // Sets whether the prompt's iconView animation has been loaded in the view yet.
-    fun setIsIconViewLoaded(iconViewLoaded: Boolean) {
-        _isIconViewLoaded.value = iconViewLoaded
-    }
-
     /** Padding for prompt UI elements */
     val promptPadding: Flow<Rect> =
         combine(size, displayStateInteractor.currentRotation) { size, rotation ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 1f69cc0..0d40511 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -397,8 +397,10 @@
             IntentFilter filter = new IntentFilter();
             filter.addAction(Intent.ACTION_DATE_CHANGED);
             filter.addAction(Intent.ACTION_LOCALE_CHANGED);
-            getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
-                    null /* scheduler */);
+            mBgHandler.post(() -> {
+                getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
+                        null /* scheduler */);
+            });
             mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
             mRegistered = true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 3925dd1..13e3835 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -34,6 +34,7 @@
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollector;
@@ -79,9 +80,12 @@
 import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
+import dagger.Binds;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
 
 import java.util.concurrent.Executor;
 
@@ -105,7 +109,7 @@
             StartKeyguardTransitionModule.class,
             ResourceTrimmerModule.class,
         })
-public class KeyguardModule {
+public interface KeyguardModule {
     /**
      * Provides our instance of KeyguardViewMediator which is considered optional.
      */
@@ -207,13 +211,19 @@
 
     /** */
     @Provides
-    public ViewMediatorCallback providesViewMediatorCallback(KeyguardViewMediator viewMediator) {
+    static ViewMediatorCallback providesViewMediatorCallback(KeyguardViewMediator viewMediator) {
         return viewMediator.getViewMediatorCallback();
     }
 
     /** */
     @Provides
-    public KeyguardQuickAffordancesMetricsLogger providesKeyguardQuickAffordancesMetricsLogger() {
+    static KeyguardQuickAffordancesMetricsLogger providesKeyguardQuickAffordancesMetricsLogger() {
         return new KeyguardQuickAffordancesMetricsLoggerImpl();
     }
+
+    /** Binds {@link KeyguardUpdateMonitor} as a {@link CoreStartable}. */
+    @Binds
+    @IntoMap
+    @ClassKey(KeyguardUpdateMonitor.class)
+    CoreStartable bindsKeyguardUpdateMonitor(KeyguardUpdateMonitor keyguardUpdateMonitor);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 633510d..c9df317 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.statusbar;
 
+import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
+import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
@@ -22,6 +24,7 @@
 import static android.os.UserHandle.USER_NULL;
 import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
 import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+import static android.app.Flags.keyguardPrivateNotifications;
 import static android.os.Flags.allowPrivateProfile;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
@@ -47,7 +50,6 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Log;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
@@ -149,6 +151,25 @@
             new ListenerSet<>();
     private final Collection<Uri> mLockScreenUris = new ArrayList<>();
 
+    protected final BroadcastReceiver mKeyguardReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+
+            if (ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED.equals(action)) {
+                if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+                    mKeyguardAllowingNotifications =
+                            intent.getBooleanExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, false);
+                    if (mCurrentUserId == getSendingUserId()) {
+                        boolean changed = updateLockscreenNotificationSetting();
+                        if (changed) {
+                            notifyNotificationStateChanged();
+                        }
+                    }
+                }
+            }
+        }
+    };
 
     protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
         @Override
@@ -321,11 +342,21 @@
         mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);
 
         dumpManager.registerDumpable(this);
+
+        if (keyguardPrivateNotifications()) {
+            init();
+        }
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter) {
         mPresenter = presenter;
 
+        if (!keyguardPrivateNotifications()) {
+            init();
+        }
+    }
+
+    private void init() {
         mLockscreenSettingsObserver = new ContentObserver(
                 mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
                         ? mBackgroundHandler
@@ -408,6 +439,11 @@
                 new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
                 mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
                         ? mBackgroundExecutor : null, UserHandle.ALL);
+        if (keyguardPrivateNotifications()) {
+            mBroadcastDispatcher.registerReceiver(mKeyguardReceiver,
+                    new IntentFilter(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED),
+                    mBackgroundExecutor, UserHandle.ALL);
+        }
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_ADDED);
@@ -449,6 +485,10 @@
         mLockscreenSettingsObserver.onChange(
                 false, mLockScreenUris, 0, UserHandle.of(userId));
         updateDpcSettings(userId);
+
+        if (keyguardPrivateNotifications()) {
+            updateGlobalKeyguardSettings();
+        }
     }
 
     public boolean shouldShowLockscreenNotifications() {
@@ -477,8 +517,12 @@
         boolean allowedByDpm;
 
         if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
-            show = mUsersUsersAllowingNotifications.get(mCurrentUserId)
-                    && mKeyguardAllowingNotifications;
+            if (keyguardPrivateNotifications()) {
+                show = mUsersUsersAllowingNotifications.get(mCurrentUserId);
+            } else {
+                show = mUsersUsersAllowingNotifications.get(mCurrentUserId)
+                        && mKeyguardAllowingNotifications;
+            }
             // If DPC never notified us about a user, that means they have no policy for the user,
             // and they allow the behavior
             allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true);
@@ -521,8 +565,13 @@
                 1,
                 userId) != 0;
         mUsersUsersAllowingNotifications.put(userId, newAllowLockscreen);
-        boolean keyguardChanged = updateGlobalKeyguardSettings();
-        return (newAllowLockscreen != originalAllowLockscreen) || keyguardChanged;
+
+        if (keyguardPrivateNotifications()) {
+            return (newAllowLockscreen != originalAllowLockscreen);
+        } else {
+            boolean keyguardChanged = updateGlobalKeyguardSettings();
+            return (newAllowLockscreen != originalAllowLockscreen) || keyguardChanged;
+        }
     }
 
     @WorkerThread
@@ -560,8 +609,14 @@
                 Log.i(TAG, "Asking for redact notifs dpm override too early", new Throwable());
                 return false;
             }
-            return mUsersUsersAllowingPrivateNotifications.get(userHandle)
-                    && mUsersDpcAllowingPrivateNotifications.get(userHandle);
+            if (keyguardPrivateNotifications()) {
+                return mUsersUsersAllowingPrivateNotifications.get(userHandle)
+                        && mUsersDpcAllowingPrivateNotifications.get(userHandle)
+                        && mKeyguardAllowingNotifications;
+            } else {
+                return mUsersUsersAllowingPrivateNotifications.get(userHandle)
+                        && mUsersDpcAllowingPrivateNotifications.get(userHandle);
+            }
         } else {
             if (userHandle == USER_ALL) {
                 return true;
@@ -648,9 +703,14 @@
                 Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable());
                 updateDpcSettings(userHandle);
             }
-            return mUsersUsersAllowingNotifications.get(userHandle)
-                    && mUsersDpcAllowingNotifications.get(userHandle)
-                    && mKeyguardAllowingNotifications;
+            if (keyguardPrivateNotifications()) {
+                return mUsersUsersAllowingNotifications.get(userHandle)
+                        && mUsersDpcAllowingNotifications.get(userHandle);
+            } else {
+                return mUsersUsersAllowingNotifications.get(userHandle)
+                        && mUsersDpcAllowingNotifications.get(userHandle)
+                        && mKeyguardAllowingNotifications;
+            }
         } else {
             if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
                 return true;
@@ -689,7 +749,12 @@
                 ent.getSbn().getNotification().visibility == Notification.VISIBILITY_PRIVATE;
         boolean userForcesRedaction = packageHasVisibilityOverride(ent.getSbn().getKey());
 
-        return userForcesRedaction || notificationRequestsRedaction && isNotifRedacted;
+        if (keyguardPrivateNotifications()) {
+            return !mKeyguardAllowingNotifications
+                    || userForcesRedaction || notificationRequestsRedaction && isNotifRedacted;
+        } else {
+            return userForcesRedaction || notificationRequestsRedaction && isNotifRedacted;
+        }
     }
 
     private boolean packageHasVisibilityOverride(String key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 70ccc4f..80ef14b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.inflation;
 
+import static com.android.systemui.Flags.screenshareNotificationHiding;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
@@ -243,7 +244,11 @@
         params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
         params.setUseLowPriority(isLowPriority);
 
-        if (mNotificationLockscreenUserManager.needsRedaction(entry)) {
+        // If screenshareNotificationHiding is enabled, both public and private views should be
+        // inflated to avoid any latency associated with reinflating all notification views when
+        // screen share starts and stops
+        if (screenshareNotificationHiding()
+                || mNotificationLockscreenUserManager.needsRedaction(entry)) {
             params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);
         } else {
             params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
index 3f76d30..8858d13 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
@@ -51,6 +51,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import dagger.Lazy
 
 @SmallTest
 class ActiveUnlockConfigTest : SysuiTestCase() {
@@ -60,6 +61,7 @@
     @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var lazyKeyguardUpdateMonitor: Lazy<KeyguardUpdateMonitor>
     @Mock private lateinit var mockPrintWriter: PrintWriter
 
     @Captor private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
@@ -72,6 +74,7 @@
         MockitoAnnotations.initMocks(this)
 
         whenever(selectedUserInteractor.getSelectedUserId()).thenReturn(currentUser)
+        whenever(lazyKeyguardUpdateMonitor.get()).thenReturn(keyguardUpdateMonitor)
         secureSettings = FakeSettings()
         activeUnlockConfig =
             ActiveUnlockConfig(
@@ -79,6 +82,7 @@
                 secureSettings,
                 contentResolver,
                 selectedUserInteractor,
+                lazyKeyguardUpdateMonitor,
                 dumpManager
             )
     }
@@ -260,7 +264,6 @@
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
 
         // GIVEN fingerprint and face are NOT enrolled
-        activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
         `when`(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(false)
         `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(false)
 
@@ -290,7 +293,6 @@
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
 
         // GIVEN fingerprint and face are both enrolled
-        activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
         `when`(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(true)
         `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(true)
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 1ab634c..d03a898 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -2283,6 +2283,7 @@
                     Optional.of(mInteractiveToAuthProvider),
                     mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
+            start();
         }
 
         public boolean hasSimStateJustChanged() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 5e57c83..bc50c25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -198,7 +198,7 @@
         AtomicBoolean isBgThreadFailed = new AtomicBoolean(false);
         Thread bgThread = new Thread(() -> {
             assertTrue(getLock(unloadLock, 10));
-            assertTrue(getLock(loadLock, 3000)); // Wait for the foreground thread
+            assertTrue(getLock(loadLock, 4000)); // Wait for the foreground thread
             assertNotNull(mPluginInstance.getPlugin());
             // Attempt to delete the plugin, this should block until the load completes
             mPluginInstance.unloadPlugin();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 42c7375..0c6f456 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -19,9 +19,12 @@
 import static android.app.Notification.VISIBILITY_PRIVATE;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
+import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
+import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
 import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
 import static android.os.UserHandle.USER_ALL;
 import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
@@ -111,7 +114,9 @@
 
     @Parameters(name = "{0}")
     public static List<FlagsParameterization> getParams() {
-        return FlagsParameterization.allCombinationsOf(FLAG_ALLOW_PRIVATE_PROFILE);
+        return FlagsParameterization.allCombinationsOf(
+                FLAG_ALLOW_PRIVATE_PROFILE,
+                FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS);
     }
 
     public NotificationLockscreenUserManagerTest(FlagsParameterization flags) {
@@ -245,6 +250,19 @@
     }
 
     @Test
+    @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+    public void testInit() {
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
+        mLockscreenUserManager.setUpWithPresenter(mPresenter);
+
+        mBackgroundExecutor.runAllReady();
+
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
     public void testGetCurrentProfiles() {
         final SparseArray<UserInfo> expectedCurProfiles = new SparseArray<>();
         expectedCurProfiles.put(mCurrentUser.id, mCurrentUser);
@@ -579,6 +597,29 @@
     }
 
     @Test
+    @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+    public void testKeyguardManager_noPrivateNotifications() {
+        Mockito.clearInvocations(mDevicePolicyManager);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED)
+                        .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, true));
+
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+        // it's a global field, confirm secondary too
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
+                mSecondaryUser.id));
+    }
+
+    @Test
     public void testDevicePolicy_noPrivateNotifications() {
         Mockito.clearInvocations(mDevicePolicyManager);
         // User allows notifications
@@ -699,6 +740,29 @@
     }
 
     @Test
+    @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+    public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications_show() {
+        // KeyguardManager does not allow notifications
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        // DevicePolicy allows notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(0);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mKeyguardReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mKeyguardReceiver.onReceive(mContext,
+                new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED)
+                        .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, false));
+
+        assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    @DisableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
     public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() {
         // KeyguardManager does not allow notifications
         when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
@@ -718,6 +782,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
     public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
         // DevicePolicy allows notifications
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
index 3a90a95..73ed97f 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
@@ -35,8 +35,8 @@
 import android.util.LocalLog;
 import android.util.Slog;
 import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.ContentCaptureSession;
 import android.view.contentcapture.ContentCaptureSessionId;
-import android.view.contentcapture.MainContentCaptureSession;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
@@ -123,7 +123,7 @@
     public void setContentCaptureEnabledLocked(boolean enabled) {
         try {
             final Bundle extras = new Bundle();
-            extras.putBoolean(MainContentCaptureSession.EXTRA_ENABLED_STATE, true);
+            extras.putBoolean(ContentCaptureSession.EXTRA_ENABLED_STATE, true);
             mSessionStateReceiver.send(enabled ? RESULT_CODE_TRUE : RESULT_CODE_FALSE, extras);
         } catch (RemoteException e) {
             Slog.w(TAG, "Error async reporting result to client: " + e);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 96b1650..02f4485 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2118,11 +2118,6 @@
                 // anyway, so we just remove the SHORT_SERVICE type.
                 foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
             }
-            if (!shouldAllowBootCompletedStart(r, foregroundServiceType)) {
-                throw new ForegroundServiceStartNotAllowedException("FGS type "
-                        + ServiceInfo.foregroundServiceTypeToLabel(foregroundServiceType)
-                        + " not allowed to start from BOOT_COMPLETED!");
-            }
 
             boolean alreadyStartedOp = false;
             boolean stopProcStatsOp = false;
@@ -2137,6 +2132,12 @@
                 mServiceFGAnrTimer.cancel(r);
             }
 
+            if (!shouldAllowBootCompletedStart(r, foregroundServiceType)) {
+                throw new ForegroundServiceStartNotAllowedException("FGS type "
+                        + ServiceInfo.foregroundServiceTypeToLabel(foregroundServiceType)
+                        + " not allowed to start from BOOT_COMPLETED!");
+            }
+
             final ProcessServiceRecord psr = r.app.mServices;
             try {
                 boolean ignoreForeground = false;
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
index 4df2581..5d609bc 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -54,8 +54,8 @@
 
     @NonNull private final Context mContext;
     @NonNull private final PackageManager mPackageManager;
-    @NonNull private final FaceManager mFaceManager;
-    @NonNull private final FingerprintManager mFingerprintManager;
+    @Nullable private final FaceManager mFaceManager;
+    @Nullable private final FingerprintManager mFingerprintManager;
 
     private final boolean mEnabled;
     private final float mThreshold;
@@ -197,11 +197,11 @@
     }
 
     private boolean hasEnrolledFace(int userId) {
-        return mFaceManager.hasEnrolledTemplates(userId);
+        return mFaceManager != null && mFaceManager.hasEnrolledTemplates(userId);
     }
 
     private boolean hasEnrolledFingerprint(int userId) {
-        return mFingerprintManager.hasEnrolledTemplates(userId);
+        return mFingerprintManager != null && mFingerprintManager.hasEnrolledTemplates(userId);
     }
 
     /**
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java
similarity index 98%
rename from services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
rename to services/core/java/com/android/server/media/AudioManagerRouteController.java
index 0eb9166..5c9e61a 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java
@@ -58,11 +58,9 @@
  * <p>This implementation obtains and manages all routes via {@link AudioManager}, with the
  * exception of {@link AudioManager#handleBluetoothActiveDeviceChanged inactive bluetooth} routes
  * which are managed by {@link AudioPoliciesBluetoothRouteController}, which depends on the
- * bluetooth stack (for example {@link BluetoothAdapter}.
+ * bluetooth stack ({@link BluetoothAdapter} and related classes).
  */
-// TODO: b/305199571 - Rename this class to avoid the AudioPolicies prefix, which has been flagged
-// by the audio team as a confusing name.
-/* package */ final class AudioPoliciesDeviceRouteController implements DeviceRouteController {
+/* package */ final class AudioManagerRouteController implements DeviceRouteController {
     private static final String TAG = SystemMediaRoute2Provider.TAG;
 
     @NonNull
@@ -103,7 +101,7 @@
                 Manifest.permission.MODIFY_AUDIO_ROUTING,
                 Manifest.permission.QUERY_AUDIO_STATE
             })
-    /* package */ AudioPoliciesDeviceRouteController(
+    /* package */ AudioManagerRouteController(
             @NonNull Context context,
             @NonNull AudioManager audioManager,
             @NonNull Looper looper,
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index 8b62cc9..dff0adf 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -65,7 +65,7 @@
         if (strategyForMedia != null
                 && btAdapter != null
                 && Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
-            return new AudioPoliciesDeviceRouteController(
+            return new AudioManagerRouteController(
                     context,
                     audioManager,
                     looper,
diff --git a/services/core/java/com/android/server/media/TEST_MAPPING b/services/core/java/com/android/server/media/TEST_MAPPING
index 1b49093..b3e5b9e 100644
--- a/services/core/java/com/android/server/media/TEST_MAPPING
+++ b/services/core/java/com/android/server/media/TEST_MAPPING
@@ -3,5 +3,10 @@
     {
       "name": "CtsMediaBetterTogetherTestCases"
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "MediaRouterServiceTests"
+    }
   ]
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ff415c1..4d7d8cf 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -16,7 +16,9 @@
 
 package com.android.server.notification;
 
+import static android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS;
 import static android.Manifest.permission.RECEIVE_SENSITIVE_NOTIFICATIONS;
+import static android.Manifest.permission.STATUS_BAR_SERVICE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
@@ -67,6 +69,8 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
 import static android.app.Flags.lifetimeExtensionRefactor;
+import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
+import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
 import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT;
 import static android.content.Context.BIND_AUTO_CREATE;
 import static android.content.Context.BIND_FOREGROUND_SERVICE;
@@ -210,6 +214,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
@@ -3369,9 +3374,7 @@
                             .setChannelName(r.getChannel().getName().toString())
                             .setPostedTimeMs(System.currentTimeMillis())
                             .setTitle(getHistoryTitle(r.getNotification()))
-                            .setText(getHistoryText(
-                                    r.getSbn().getPackageContext(getContext()),
-                                    r.getNotification()))
+                            .setText(getHistoryText(r.getNotification()))
                             .setIcon(r.getNotification().getSmallIcon())
                             .build());
                 }
@@ -3414,12 +3417,11 @@
     /**
      * Returns the appropriate substring for this notification based on the style of notification.
      */
-    private String getHistoryText(Context appContext, Notification n) {
+    private String getHistoryText(Notification n) {
         CharSequence text = null;
         if (n.extras != null) {
             text = n.extras.getCharSequence(EXTRA_TEXT);
-
-            Notification.Builder nb = Notification.Builder.recoverBuilder(appContext, n);
+            Notification.Builder nb = Notification.Builder.recoverBuilder(getContext(), n);
 
             if (nb.getStyle() instanceof Notification.BigTextStyle) {
                 text = ((Notification.BigTextStyle) nb.getStyle()).getBigText();
@@ -5569,7 +5571,7 @@
 
         private void enforceSystemOrSystemUI(String message) {
             if (isCallerSystemOrPhone()) return;
-            getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+            getContext().enforceCallingPermission(STATUS_BAR_SERVICE,
                     message);
         }
 
@@ -5578,7 +5580,7 @@
                 checkCallerIsSystemOrSameApp(pkg);
             } catch (SecurityException e) {
                 getContext().enforceCallingPermission(
-                        android.Manifest.permission.STATUS_BAR_SERVICE,
+                        STATUS_BAR_SERVICE,
                         message);
             }
         }
@@ -6183,13 +6185,20 @@
         @Override
         public void setPrivateNotificationsAllowed(boolean allow) {
             if (PackageManager.PERMISSION_GRANTED
-                    != getContext().checkCallingPermission(
-                            permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
+                    != getContext().checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
                 throw new SecurityException(
                         "Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission");
             }
             if (allow != mLockScreenAllowSecureNotifications) {
                 mLockScreenAllowSecureNotifications = allow;
+                if (android.app.Flags.keyguardPrivateNotifications()) {
+                    getContext().sendBroadcast(
+                            new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED)
+                                    .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED,
+                                            mLockScreenAllowSecureNotifications),
+                            STATUS_BAR_SERVICE);
+                }
+
                 handleSavePolicyFile();
             }
         }
@@ -6197,8 +6206,7 @@
         @Override
         public boolean getPrivateNotificationsAllowed() {
             if (PackageManager.PERMISSION_GRANTED
-                    != getContext().checkCallingPermission(
-                            permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
+                    != getContext().checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
                 throw new SecurityException(
                         "Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission");
             }
@@ -8369,6 +8377,8 @@
             boolean posted = false;
             try {
                 posted = postNotification();
+            }  catch (Exception e) {
+                Slog.e(TAG, "Error posting", e);
             } finally {
                 if (!posted) {
                     mTracker.cancel();
@@ -9557,12 +9567,16 @@
     }
 
     private void scheduleListenerHintsChanged(int state) {
-        mHandler.removeMessages(MESSAGE_LISTENER_HINTS_CHANGED);
+        if (!Flags.notificationReduceMessagequeueUsage()) {
+            mHandler.removeMessages(MESSAGE_LISTENER_HINTS_CHANGED);
+        }
         mHandler.obtainMessage(MESSAGE_LISTENER_HINTS_CHANGED, state, 0).sendToTarget();
     }
 
     private void scheduleInterruptionFilterChanged(int listenerInterruptionFilter) {
-        mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED);
+        if (!Flags.notificationReduceMessagequeueUsage()) {
+            mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED);
+        }
         mHandler.obtainMessage(
                 MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED,
                 listenerInterruptionFilter,
@@ -9642,15 +9656,24 @@
         }
 
         protected void scheduleSendRankingUpdate() {
-            if (!hasMessages(MESSAGE_SEND_RANKING_UPDATE)) {
+            if (Flags.notificationReduceMessagequeueUsage()) {
                 Message m = Message.obtain(this, MESSAGE_SEND_RANKING_UPDATE);
                 sendMessage(m);
+            } else {
+                if (!hasMessages(MESSAGE_SEND_RANKING_UPDATE)) {
+                    Message m = Message.obtain(this, MESSAGE_SEND_RANKING_UPDATE);
+                    sendMessage(m);
+                }
             }
         }
 
         protected void scheduleCancelNotification(CancelNotificationRunnable cancelRunnable) {
-            if (!hasCallbacks(cancelRunnable)) {
+            if (Flags.notificationReduceMessagequeueUsage()) {
                 sendMessage(Message.obtain(this, cancelRunnable));
+            } else {
+                if (!hasCallbacks(cancelRunnable)) {
+                    sendMessage(Message.obtain(this, cancelRunnable));
+                }
             }
         }
 
@@ -9684,7 +9707,9 @@
         }
 
         public void requestSort() {
-            removeMessages(MESSAGE_RANKING_SORT);
+            if (!Flags.notificationReduceMessagequeueUsage()) {
+                removeMessages(MESSAGE_RANKING_SORT);
+            }
             Message msg = Message.obtain();
             msg.what = MESSAGE_RANKING_SORT;
             sendMessage(msg);
@@ -10589,7 +10614,7 @@
         if (isCallerSystemOrPhone()) {
             return true;
         }
-        return getContext().checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+        return getContext().checkCallingPermission(STATUS_BAR_SERVICE)
                 == PERMISSION_GRANTED;
     }
 
@@ -10628,7 +10653,7 @@
         if (isCallerSystemOrPhone()) {
             return;
         }
-        getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+        getContext().enforceCallingPermission(STATUS_BAR_SERVICE,
                 message);
     }
 
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 8e79922a..49db7fc 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -50,3 +50,10 @@
   # Referenced in WM where WM starts before DeviceConfig
   is_fixed_read_only: true
 }
+
+flag {
+  name: "notification_reduce_messagequeue_usage"
+  namespace: "systemui"
+  description: "When this flag is on, NMS will no longer call removeMessage() and hasCallbacks() on Handler"
+  bug: "311051285"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index e546f42..1660c3e 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -21,11 +21,13 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.os.Binder;
 import android.os.BugreportManager.BugreportCallback;
 import android.os.BugreportParams;
@@ -37,7 +39,6 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -95,7 +96,6 @@
     private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
 
     private final Object mLock = new Object();
-    private final Injector mInjector;
     private final Context mContext;
     private final AppOpsManager mAppOps;
     private final TelephonyManager mTelephonyManager;
@@ -346,14 +346,6 @@
         AtomicFile getMappingFile() {
             return mMappingFile;
         }
-
-        UserManager getUserManager() {
-            return mContext.getSystemService(UserManager.class);
-        }
-
-        DevicePolicyManager getDevicePolicyManager() {
-            return mContext.getSystemService(DevicePolicyManager.class);
-        }
     }
 
     BugreportManagerServiceImpl(Context context) {
@@ -365,7 +357,6 @@
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     BugreportManagerServiceImpl(Injector injector) {
-        mInjector = injector;
         mContext = injector.getContext();
         mAppOps = mContext.getSystemService(AppOpsManager.class);
         mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
@@ -398,7 +389,12 @@
         int callingUid = Binder.getCallingUid();
         enforcePermission(callingPackage, callingUid, bugreportMode
                 == BugreportParams.BUGREPORT_MODE_TELEPHONY /* checkCarrierPrivileges */);
-        ensureUserCanTakeBugReport(bugreportMode);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            ensureUserCanTakeBugReport(bugreportMode);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
 
         Slogf.i(TAG, "Starting bugreport for %s / %d", callingPackage, callingUid);
         synchronized (mLock) {
@@ -437,6 +433,7 @@
     @RequiresPermission(value = Manifest.permission.DUMP, conditional = true)
     public void retrieveBugreport(int callingUidUnused, String callingPackage, int userId,
             FileDescriptor bugreportFd, String bugreportFile,
+
             boolean keepBugreportOnRetrievalUnused, IDumpstateListener listener) {
         int callingUid = Binder.getCallingUid();
         enforcePermission(callingPackage, callingUid, false);
@@ -568,48 +565,54 @@
     }
 
     /**
-     * Validates that the calling user is an admin user or, when bugreport is requested remotely
-     * that the user is an affiliated user.
+     * Validates that the current user is an admin user or, when bugreport is requested remotely
+     * that the current user is an affiliated user.
      *
-     * @throws IllegalArgumentException if the calling user is not an admin user
+     * @throws IllegalArgumentException if the current user is not an admin user
      */
     private void ensureUserCanTakeBugReport(int bugreportMode) {
-        // Get the calling userId before clearing the caller identity.
-        int callingUserId = UserHandle.getUserId(Binder.getCallingUid());
-        boolean isAdminUser = false;
-        final long identity = Binder.clearCallingIdentity();
+        UserInfo currentUser = null;
         try {
-            isAdminUser = mInjector.getUserManager().isUserAdmin(callingUserId);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+            currentUser = ActivityManager.getService().getCurrentUser();
+        } catch (RemoteException e) {
+            // Impossible to get RemoteException for an in-process call.
         }
-        if (!isAdminUser) {
+
+        if (currentUser == null) {
+            logAndThrow("There is no current user, so no bugreport can be requested.");
+        }
+
+        if (!currentUser.isAdmin()) {
             if (bugreportMode == BugreportParams.BUGREPORT_MODE_REMOTE
-                    && isUserAffiliated(callingUserId)) {
+                    && isCurrentUserAffiliated(currentUser.id)) {
                 return;
             }
-            logAndThrow(TextUtils.formatSimple("Calling user %s is not an admin user."
-                    + " Only admin users are allowed to take bugreport.", callingUserId));
+            logAndThrow(TextUtils.formatSimple("Current user %s is not an admin user."
+                    + " Only admin users are allowed to take bugreport.", currentUser.id));
         }
     }
 
     /**
-     * Returns {@code true} if the device has device owner and the specified user is affiliated
+     * Returns {@code true} if the device has device owner and the current user is affiliated
      * with the device owner.
      */
-    private boolean isUserAffiliated(int userId) {
-        DevicePolicyManager dpm = mInjector.getDevicePolicyManager();
+    private boolean isCurrentUserAffiliated(int currentUserId) {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
         int deviceOwnerUid = dpm.getDeviceOwnerUserId();
         if (deviceOwnerUid == UserHandle.USER_NULL) {
             return false;
         }
 
-        if (DEBUG) {
-            Slog.d(TAG, "callingUid: " + userId + " deviceOwnerUid: " + deviceOwnerUid);
-        }
+        int callingUserId = UserHandle.getUserId(Binder.getCallingUid());
 
-        if (userId != deviceOwnerUid && !dpm.isAffiliatedUser(userId)) {
-            logAndThrow("User " + userId + " is not affiliated to the device owner.");
+        Slog.i(TAG, "callingUid: " + callingUserId + " deviceOwnerUid: " + deviceOwnerUid
+                + " currentUserId: " + currentUserId);
+
+        if (callingUserId != deviceOwnerUid) {
+            logAndThrow("Caller is not device owner on provisioned device.");
+        }
+        if (!dpm.isAffiliatedUser(currentUserId)) {
+            logAndThrow("Current user is not affiliated to the device owner.");
         }
         return true;
     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5b13d3fe..edce3ec 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -473,6 +473,8 @@
 
     private TalkbackShortcutController mTalkbackShortcutController;
 
+    private WindowWakeUpPolicy mWindowWakeUpPolicy;
+
     boolean mSafeMode;
 
     // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
@@ -640,15 +642,6 @@
     // Whether to lock the device after the next dreaming transition has finished.
     private boolean mLockAfterDreamingTransitionFinished;
 
-    // Allowed theater mode wake actions
-    private boolean mAllowTheaterModeWakeFromKey;
-    private boolean mAllowTheaterModeWakeFromPowerKey;
-    private boolean mAllowTheaterModeWakeFromMotion;
-    private boolean mAllowTheaterModeWakeFromMotionWhenNotDreaming;
-    private boolean mAllowTheaterModeWakeFromCameraLens;
-    private boolean mAllowTheaterModeWakeFromLidSwitch;
-    private boolean mAllowTheaterModeWakeFromWakeGesture;
-
     // If true, the power button long press behavior will be invoked even if the default display is
     // non-interactive. If false, the power button long press behavior will be skipped if the
     // default display is non-interactive.
@@ -930,8 +923,7 @@
                 if (shouldEnableWakeGestureLp()) {
                     performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false,
                             "Wake Up");
-                    wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromWakeGesture,
-                            PowerManager.WAKE_REASON_GESTURE, "android.policy:GESTURE");
+                    mWindowWakeUpPolicy.wakeUpFromWakeGesture();
                 }
             }
         }
@@ -1067,7 +1059,7 @@
                 || handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted();
         if (!mPowerKeyHandled) {
             if (!interactive) {
-                wakeUpFromPowerKey(event.getDownTime());
+                wakeUpFromWakeKey(event);
             }
         } else {
             // handled by another power key policy.
@@ -1309,7 +1301,7 @@
                     Settings.Global.putInt(mContext.getContentResolver(),
                             Settings.Global.THEATER_MODE_ON, 0);
                     if (!interactive) {
-                        wakeUpFromPowerKey(eventTime);
+                        wakeUpFromWakeKey(eventTime, KEYCODE_POWER);
                     }
                 } else {
                     Slog.i(TAG, "Toggling theater mode on.");
@@ -1325,7 +1317,7 @@
             case MULTI_PRESS_POWER_BRIGHTNESS_BOOST:
                 Slog.i(TAG, "Starting brightness boost.");
                 if (!interactive) {
-                    wakeUpFromPowerKey(eventTime);
+                    wakeUpFromWakeKey(eventTime, KEYCODE_POWER);
                 }
                 mPowerManager.boostScreenBrightness(eventTime);
                 break;
@@ -2312,22 +2304,6 @@
         mLidNavigationAccessibility = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_lidNavigationAccessibility);
 
-        mAllowTheaterModeWakeFromKey = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_allowTheaterModeWakeFromKey);
-        mAllowTheaterModeWakeFromPowerKey = mAllowTheaterModeWakeFromKey
-                || mContext.getResources().getBoolean(
-                    com.android.internal.R.bool.config_allowTheaterModeWakeFromPowerKey);
-        mAllowTheaterModeWakeFromMotion = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_allowTheaterModeWakeFromMotion);
-        mAllowTheaterModeWakeFromMotionWhenNotDreaming = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_allowTheaterModeWakeFromMotionWhenNotDreaming);
-        mAllowTheaterModeWakeFromCameraLens = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraLens);
-        mAllowTheaterModeWakeFromLidSwitch = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch);
-        mAllowTheaterModeWakeFromWakeGesture = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture);
-
         mGoToSleepOnButtonPressTheaterMode = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_goToSleepOnButtonPressTheaterMode);
 
@@ -2457,6 +2433,7 @@
                 com.android.internal.R.integer.config_keyguardDrawnTimeout);
         mKeyguardDelegate = injector.getKeyguardServiceDelegate();
         mTalkbackShortcutController = injector.getTalkbackShortcutController();
+        mWindowWakeUpPolicy = new WindowWakeUpPolicy(mContext);
         initKeyCombinationRules();
         initSingleKeyGestureRules(injector.getLooper());
         mButtonOverridePermissionChecker = injector.getButtonOverridePermissionChecker();
@@ -4483,8 +4460,7 @@
         updateRotation(true);
 
         if (lidOpen) {
-            wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromLidSwitch,
-                    PowerManager.WAKE_REASON_LID, "android.policy:LID");
+            mWindowWakeUpPolicy.wakeUpFromLid();
         } else if (getLidBehavior() != LID_BEHAVIOR_SLEEP) {
             mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
         }
@@ -4510,8 +4486,7 @@
             } else {
                 intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
             }
-            wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromCameraLens,
-                    PowerManager.WAKE_REASON_CAMERA_LAUNCH, "android.policy:CAMERA_COVER");
+            mWindowWakeUpPolicy.wakeUpFromCameraCover(whenNanos / 1000000);
             startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
         }
         mCameraLensCoverState = lensCoverState;
@@ -4589,7 +4564,7 @@
             boolean shouldTurnOnTv = false;
             if (down && (keyCode == KeyEvent.KEYCODE_POWER
                     || keyCode == KeyEvent.KEYCODE_TV_POWER)) {
-                wakeUpFromPowerKey(event.getDownTime());
+                wakeUpFromWakeKey(event);
                 shouldTurnOnTv = true;
             } else if (down && (isWakeKey || keyCode == KeyEvent.KEYCODE_WAKEUP)
                     && isWakeKeyWhenScreenOff(keyCode)) {
@@ -5104,9 +5079,7 @@
         if (mRequestedOrSleepingDefaultDisplay) {
             mCameraGestureTriggeredDuringGoingToSleep = true;
             // Wake device up early to prevent display doing redundant turning off/on stuff.
-            wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromPowerKey,
-                    PowerManager.WAKE_REASON_CAMERA_LAUNCH,
-                    "android.policy:CAMERA_GESTURE_PREVENT_LOCK");
+            mWindowWakeUpPolicy.wakeUpFromPowerKeyCameraGesture();
         }
         return true;
     }
@@ -5204,8 +5177,7 @@
     public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
             long whenNanos, int policyFlags) {
         if ((policyFlags & FLAG_WAKE) != 0) {
-            if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion,
-                    PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) {
+            if (mWindowWakeUpPolicy.wakeUpFromMotion(whenNanos / 1000000)) {
                 // Woke up. Pass motion events to user.
                 return ACTION_PASS_TO_USER;
             }
@@ -5219,8 +5191,7 @@
         // there will be no dream to intercept the touch and wake into ambient.  The device should
         // wake up in this case.
         if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) {
-            if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotionWhenNotDreaming,
-                    PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) {
+            if (mWindowWakeUpPolicy.wakeUpFromMotion(whenNanos / 1000000)) {
                 // Woke up. Pass motion events to user.
                 return ACTION_PASS_TO_USER;
             }
@@ -5554,37 +5525,22 @@
         return sleepDurationRealtime > mWakeUpToLastStateTimeout;
     }
 
-    private void wakeUpFromPowerKey(long eventTime) {
-        if (wakeUp(eventTime, mAllowTheaterModeWakeFromPowerKey,
-                PowerManager.WAKE_REASON_POWER_BUTTON, "android.policy:POWER")) {
-            // Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout
-            if (shouldWakeUpWithHomeIntent()) {
-                startDockOrHome(DEFAULT_DISPLAY, /*fromHomeKey*/ false, /*wakenFromDreams*/ true,
-                        PowerManager.wakeReasonToString(PowerManager.WAKE_REASON_POWER_BUTTON));
-            }
-        }
-    }
-
     private void wakeUpFromWakeKey(KeyEvent event) {
-        if (wakeUp(event.getEventTime(), mAllowTheaterModeWakeFromKey,
-                PowerManager.WAKE_REASON_WAKE_KEY, "android.policy:KEY")) {
-            // Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout
-            if (shouldWakeUpWithHomeIntent() && event.getKeyCode() == KEYCODE_HOME) {
-                startDockOrHome(DEFAULT_DISPLAY, /*fromHomeKey*/ true, /*wakenFromDreams*/ true,
-                        PowerManager.wakeReasonToString(PowerManager.WAKE_REASON_WAKE_KEY));
-            }
-        }
+        wakeUpFromWakeKey(event.getEventTime(), event.getKeyCode());
     }
 
-    private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode, @WakeReason int reason,
-            String details) {
-        final boolean theaterModeEnabled = isTheaterModeEnabled();
-        if (!wakeInTheaterMode && theaterModeEnabled) {
-            return false;
+    private void wakeUpFromWakeKey(long eventTime, int keyCode) {
+        if (mWindowWakeUpPolicy.wakeUpFromKey(eventTime, keyCode)) {
+            final boolean keyCanLaunchHome = keyCode == KEYCODE_HOME || keyCode == KEYCODE_POWER;
+            // Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout
+            if (shouldWakeUpWithHomeIntent() &&  keyCanLaunchHome) {
+                startDockOrHome(
+                        DEFAULT_DISPLAY,
+                        /*fromHomeKey*/ keyCode == KEYCODE_HOME,
+                        /*wakenFromDreams*/ true,
+                        "Wake from " + KeyEvent. keyCodeToString(keyCode));
+            }
         }
-
-        mPowerManager.wakeUp(wakeTime, reason, details);
-        return true;
     }
 
     private void finishKeyguardDrawn() {
diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
new file mode 100644
index 0000000..392d0d4
--- /dev/null
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static android.os.PowerManager.WAKE_REASON_CAMERA_LAUNCH;
+import static android.os.PowerManager.WAKE_REASON_GESTURE;
+import static android.os.PowerManager.WAKE_REASON_LID;
+import static android.os.PowerManager.WAKE_REASON_POWER_BUTTON;
+import static android.os.PowerManager.WAKE_REASON_WAKE_KEY;
+import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
+import static android.view.KeyEvent.KEYCODE_POWER;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeReason;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.Slog;
+
+
+/** Policy controlling the decision and execution of window-related wake ups. */
+class WindowWakeUpPolicy {
+    private static final String TAG = "WindowWakeUpPolicy";
+
+    private static final boolean DEBUG = false;
+
+    private final Context mContext;
+    private final PowerManager mPowerManager;
+
+    private final boolean mAllowTheaterModeWakeFromKey;
+    private final boolean mAllowTheaterModeWakeFromPowerKey;
+    private final boolean mAllowTheaterModeWakeFromMotion;
+    private final boolean mAllowTheaterModeWakeFromMotionWhenNotDreaming;
+    private final boolean mAllowTheaterModeWakeFromCameraLens;
+    private final boolean mAllowTheaterModeWakeFromLidSwitch;
+    private final boolean mAllowTheaterModeWakeFromWakeGesture;
+
+    WindowWakeUpPolicy(Context context) {
+        mContext = context;
+        mPowerManager = context.getSystemService(PowerManager.class);
+
+        final Resources res = context.getResources();
+        mAllowTheaterModeWakeFromKey = res.getBoolean(
+                com.android.internal.R.bool.config_allowTheaterModeWakeFromKey);
+        mAllowTheaterModeWakeFromPowerKey = mAllowTheaterModeWakeFromKey
+                || res.getBoolean(
+                    com.android.internal.R.bool.config_allowTheaterModeWakeFromPowerKey);
+        mAllowTheaterModeWakeFromMotion = res.getBoolean(
+                com.android.internal.R.bool.config_allowTheaterModeWakeFromMotion);
+        mAllowTheaterModeWakeFromMotionWhenNotDreaming = res.getBoolean(
+                com.android.internal.R.bool.config_allowTheaterModeWakeFromMotionWhenNotDreaming);
+        mAllowTheaterModeWakeFromCameraLens = res.getBoolean(
+                com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraLens);
+        mAllowTheaterModeWakeFromLidSwitch = res.getBoolean(
+                com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch);
+        mAllowTheaterModeWakeFromWakeGesture = res.getBoolean(
+                com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture);
+    }
+
+    /**
+     * Wakes up from a key event.
+     *
+     * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
+     * @param keyCode the {@link android.view.KeyEvent} key code of the key event.
+     * @return {@code true} if the policy allows the requested wake up and the request has been
+     *      executed; {@code false} otherwise.
+     */
+    boolean wakeUpFromKey(long eventTime, int keyCode) {
+        final boolean wakeAllowedDuringTheaterMode =
+                keyCode == KEYCODE_POWER
+                        ? mAllowTheaterModeWakeFromPowerKey
+                        : mAllowTheaterModeWakeFromKey;
+        return wakeUp(
+                eventTime,
+                wakeAllowedDuringTheaterMode,
+                keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
+                keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+    }
+
+    /**
+     * Wakes up from a motion event.
+     *
+     * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
+     * @return {@code true} if the policy allows the requested wake up and the request has been
+     *      executed; {@code false} otherwise.
+     */
+    boolean wakeUpFromMotion(long eventTime) {
+        return wakeUp(
+                eventTime, mAllowTheaterModeWakeFromMotion, WAKE_REASON_WAKE_MOTION, "MOTION");
+    }
+
+    /**
+     * Wakes up due to an opened camera cover.
+     *
+     * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
+     * @return {@code true} if the policy allows the requested wake up and the request has been
+     *      executed; {@code false} otherwise.
+     */
+    boolean wakeUpFromCameraCover(long eventTime) {
+        return wakeUp(
+                eventTime,
+                mAllowTheaterModeWakeFromCameraLens,
+                WAKE_REASON_CAMERA_LAUNCH,
+                "CAMERA_COVER");
+    }
+
+    /**
+     * Wakes up due to an opened lid.
+     *
+     * @return {@code true} if the policy allows the requested wake up and the request has been
+     *      executed; {@code false} otherwise.
+     */
+    boolean wakeUpFromLid() {
+        return wakeUp(
+                SystemClock.uptimeMillis(),
+                mAllowTheaterModeWakeFromLidSwitch,
+                WAKE_REASON_LID,
+                "LID");
+    }
+
+    /**
+     * Wakes up to prevent sleeping when opening camera through power button.
+     *
+     * @return {@code true} if the policy allows the requested wake up and the request has been
+     *      executed; {@code false} otherwise.
+     */
+    boolean wakeUpFromPowerKeyCameraGesture() {
+        return wakeUp(
+                SystemClock.uptimeMillis(),
+                mAllowTheaterModeWakeFromPowerKey,
+                WAKE_REASON_CAMERA_LAUNCH,
+                "CAMERA_GESTURE_PREVENT_LOCK");
+    }
+
+    /**
+     * Wake up from a wake gesture.
+     *
+     * @return {@code true} if the policy allows the requested wake up and the request has been
+     *      executed; {@code false} otherwise.
+     */
+    boolean wakeUpFromWakeGesture() {
+        return wakeUp(
+                SystemClock.uptimeMillis(),
+                mAllowTheaterModeWakeFromWakeGesture,
+                WAKE_REASON_GESTURE,
+                "GESTURE");
+    }
+
+    private boolean wakeUp(
+            long wakeTime, boolean wakeInTheaterMode, @WakeReason int reason, String details) {
+        final boolean isTheaterModeEnabled =
+                Settings.Global.getInt(
+                        mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0) == 1;
+        if (!wakeInTheaterMode && isTheaterModeEnabled) {
+            if (DEBUG) Slog.d(TAG, "Unable to wake up from " + details);
+            return false;
+        }
+        mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
+        return true;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ca66a66..6033220 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -152,6 +152,7 @@
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.utils.Slogf;
+import com.android.window.flags.Flags;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -795,6 +796,14 @@
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
+        if (Flags.bundleClientTransactionFlag()) {
+            // mWmService.mResizingWindows is populated in #applySurfaceChangesTransaction()
+            handleResizingWindows();
+
+            // Called after #handleResizingWindows to include WindowStateResizeItem if any.
+            mWmService.mAtmService.getLifecycleManager().dispatchPendingTransactions();
+        }
+
         // Send any pending task-info changes that were queued-up during a layout deferment
         mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
         mWmService.mAtmService.mTaskFragmentOrganizerController.dispatchPendingEvents();
@@ -838,12 +847,11 @@
             }
         }
 
-        handleResizingWindows();
+        if (!Flags.bundleClientTransactionFlag()) {
+            handleResizingWindows();
+        }
         clearFrameChangingWindows();
 
-        // Called after #handleResizingWindows to include WindowStateResizeItem if any.
-        mWmService.mAtmService.getLifecycleManager().dispatchPendingTransactions();
-
         if (mWmService.mDisplayFrozen) {
             ProtoLog.v(WM_DEBUG_ORIENTATION,
                     "With display frozen, orientationChangeComplete=%b",
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index dda33f3..502912a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5748,7 +5748,6 @@
                 case INSETS_CHANGED: {
                     synchronized (mGlobalLock) {
                         if (mWindowsInsetsChanged > 0) {
-                            mWindowsInsetsChanged = 0;
                             // We need to update resizing windows and dispatch the new insets state
                             // to them.
                             mWindowPlacerLocked.performSurfacePlacement();
@@ -6848,6 +6847,7 @@
                     pw.println(defaultDisplayContent.getLastOrientation());
             pw.print("  mWaitingForConfig=");
                     pw.println(defaultDisplayContent.mWaitingForConfig);
+            pw.print("  mWindowsInsetsChanged="); pw.println(mWindowsInsetsChanged);
             mRotationWatcherController.dump(pw);
 
             pw.print("  Animation settings: disabled="); pw.print(mAnimationsDisabled);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 58ade1b..949025c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1443,16 +1443,7 @@
                         this, mWindowFrames.getInsetsChangedInfo(),
                         configChanged, didFrameInsetsChange);
 
-            if (insetsChanged) {
-                mWindowFrames.setInsetsChanged(false);
-                if (mWmService.mWindowsInsetsChanged > 0) {
-                    mWmService.mWindowsInsetsChanged--;
-                }
-                if (mWmService.mWindowsInsetsChanged == 0) {
-                    mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED);
-                }
-            }
-
+            consumeInsetsChange();
             onResizeHandled();
             mWmService.makeWindowFreezingScreenIfNeededLocked(this);
 
@@ -2349,6 +2340,8 @@
 
         mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
                 getWindowToken());
+
+        consumeInsetsChange();
     }
 
     @Override
@@ -3722,6 +3715,16 @@
         return mClient instanceof IWindow.Stub;
     }
 
+    private void consumeInsetsChange() {
+        if (mWindowFrames.hasInsetsChanged()) {
+            mWindowFrames.setInsetsChanged(false);
+            mWmService.mWindowsInsetsChanged--;
+            if (mWmService.mWindowsInsetsChanged == 0) {
+                mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED);
+            }
+        }
+    }
+
     /**
      * Called when the insets state changed.
      */
@@ -3729,10 +3732,10 @@
         ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsChanged for %s ", this);
         if (!mWindowFrames.hasInsetsChanged()) {
             mWindowFrames.setInsetsChanged(true);
+            mWmService.mWindowsInsetsChanged++;
 
             // If the new InsetsState won't be dispatched before releasing WM lock, the following
             // message will be executed.
-            mWmService.mWindowsInsetsChanged++;
             mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED);
             mWmService.mH.sendEmptyMessage(WindowManagerService.H.INSETS_CHANGED);
         }
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 4a2e1cb..686b2a8 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -65,7 +65,6 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.content.PackageMonitor;
 import com.android.server.credentials.metrics.ApiName;
 import com.android.server.credentials.metrics.ApiStatus;
 import com.android.server.infra.AbstractMasterSystemService;
@@ -1204,6 +1203,9 @@
         // If the app being removed matches any of the package names from
         // this list then don't add it in the output.
         Set<String> providers = new HashSet<>();
+        if (rawProviders == null || packageName == null) {
+            return providers;
+        }
         for (String rawComponentName : rawProviders.split(":")) {
             if (TextUtils.isEmpty(rawComponentName)
                     || rawComponentName.equals("null")) {
diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
similarity index 98%
rename from services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
rename to services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
index 6f9b6fa..8f5d125 100644
--- a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
+++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
@@ -62,7 +62,7 @@
 import java.util.Set;
 
 @RunWith(JUnit4.class)
-public class AudioPoliciesDeviceRouteControllerTest {
+public class AudioManagerRouteControllerTest {
 
     private static final String FAKE_ROUTE_NAME = "fake name";
     private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER =
@@ -89,7 +89,7 @@
     private Set<AudioDeviceInfo> mAvailableAudioDeviceInfos;
     @Mock private AudioManager mMockAudioManager;
     @Mock private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
-    private AudioPoliciesDeviceRouteController mControllerUnderTest;
+    private AudioManagerRouteController mControllerUnderTest;
     private AudioDeviceCallback mAudioDeviceCallback;
     private AudioProductStrategy mMediaAudioProductStrategy;
 
@@ -116,7 +116,7 @@
         BluetoothAdapter btAdapter =
                 realContext.getSystemService(BluetoothManager.class).getAdapter();
         mControllerUnderTest =
-                new AudioPoliciesDeviceRouteController(
+                new AudioManagerRouteController(
                         mockContext,
                         mMockAudioManager,
                         Looper.getMainLooper(),
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
index d2e83e9..9eeb4f3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -271,6 +271,7 @@
                 .thenReturn(true);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
         when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+        when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(null);
 
         mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
 
diff --git a/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java
index fd1abff..d850c73 100644
--- a/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java
@@ -53,6 +53,12 @@
     }
 
     @Test
+    public void getStoredProviders_nullValue_success() {
+        Set<String> providers = CredentialManagerService.getStoredProviders(null, null);
+        assertThat(providers.size()).isEqualTo(0);
+    }
+
+    @Test
     public void getStoredProviders_success() {
         Set<String> providers =
                 CredentialManagerService.getStoredProviders(
diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
index 0961b7d..eb78961 100644
--- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
@@ -70,7 +70,6 @@
                 DeviceRouteController.createInstance(
                         mContext, Looper.getMainLooper(), mOnDeviceRouteChangedListener);
 
-        Truth.assertThat(deviceRouteController)
-                .isInstanceOf(AudioPoliciesDeviceRouteController.class);
+        Truth.assertThat(deviceRouteController).isInstanceOf(AudioManagerRouteController.class);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index 21b8a94..dc1d2c5 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -16,26 +16,23 @@
 
 package com.android.server.os;
 
+import android.app.admin.flags.Flags;
+import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
+
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.when;
 
-import android.app.admin.DevicePolicyManager;
-import android.app.admin.flags.Flags;
 import android.app.role.RoleManager;
 import android.content.Context;
 import android.os.Binder;
 import android.os.BugreportManager.BugreportCallback;
-import android.os.BugreportParams;
 import android.os.IBinder;
 import android.os.IDumpstateListener;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.UserManager;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -51,8 +48,6 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
 import java.io.FileDescriptor;
 import java.util.concurrent.CompletableFuture;
@@ -71,11 +66,6 @@
     private BugreportManagerServiceImpl mService;
     private BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
 
-    @Mock
-    private UserManager mMockUserManager;
-    @Mock
-    private DevicePolicyManager mMockDevicePolicyManager;
-
     private int mCallingUid = 1234;
     private String mCallingPackage  = "test.package";
     private AtomicFile mMappingFile;
@@ -85,17 +75,14 @@
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
         mMappingFile = new AtomicFile(mContext.getFilesDir(), "bugreport-mapping.xml");
         ArraySet<String> mAllowlistedPackages = new ArraySet<>();
         mAllowlistedPackages.add(mContext.getPackageName());
         mService = new BugreportManagerServiceImpl(
-                new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
-                        mMockUserManager, mMockDevicePolicyManager));
+                new BugreportManagerServiceImpl.Injector(mContext, mAllowlistedPackages,
+                        mMappingFile));
         mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
-        // The calling user is an admin user by default.
-        when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(true);
     }
 
     @After
@@ -178,36 +165,6 @@
     }
 
     @Test
-    public void testStartBugreport_throwsForNonAdminUser() throws Exception {
-        when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
-
-        Exception thrown = assertThrows(Exception.class,
-                () -> mService.startBugreport(mCallingUid, mContext.getPackageName(),
-                        new FileDescriptor(), /* screenshotFd= */ null,
-                        BugreportParams.BUGREPORT_MODE_FULL,
-                        /* flags= */ 0, new Listener(new CountDownLatch(1)),
-                        /* isScreenshotRequested= */ false));
-
-        assertThat(thrown.getMessage()).contains("not an admin user");
-    }
-
-    @Test
-    public void testStartBugreport_throwsForNotAffiliatedUser() throws Exception {
-        when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
-        when(mMockDevicePolicyManager.getDeviceOwnerUserId()).thenReturn(-1);
-        when(mMockDevicePolicyManager.isAffiliatedUser(anyInt())).thenReturn(false);
-
-        Exception thrown = assertThrows(Exception.class,
-                () -> mService.startBugreport(mCallingUid, mContext.getPackageName(),
-                        new FileDescriptor(), /* screenshotFd= */ null,
-                        BugreportParams.BUGREPORT_MODE_REMOTE,
-                        /* flags= */ 0, new Listener(new CountDownLatch(1)),
-                        /* isScreenshotRequested= */ false));
-
-        assertThat(thrown.getMessage()).contains("not affiliated to the device owner");
-    }
-
-    @Test
     public void testRetrieveBugreportWithoutFilesForCaller() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         Listener listener = new Listener(latch);
@@ -250,8 +207,7 @@
 
     private void clearAllowlist() {
         mService = new BugreportManagerServiceImpl(
-                new TestInjector(mContext, new ArraySet<>(), mMappingFile,
-                        mMockUserManager, mMockDevicePolicyManager));
+                new BugreportManagerServiceImpl.Injector(mContext, new ArraySet<>(), mMappingFile));
     }
 
     private static class Listener implements IDumpstateListener {
@@ -302,27 +258,4 @@
             complete(successful);
         }
     }
-
-    private static class TestInjector extends BugreportManagerServiceImpl.Injector {
-
-        private final UserManager mUserManager;
-        private final DevicePolicyManager mDevicePolicyManager;
-
-        TestInjector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile,
-                UserManager um, DevicePolicyManager dpm) {
-            super(context, allowlistedPackages, mappingFile);
-            mUserManager = um;
-            mDevicePolicyManager = dpm;
-        }
-
-        @Override
-        public UserManager getUserManager() {
-            return mUserManager;
-        }
-
-        @Override
-        public DevicePolicyManager getDevicePolicyManager() {
-            return mDevicePolicyManager;
-        }
-    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 3ab7496..0b459f6 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -16,11 +16,14 @@
 
 package com.android.server.notification;
 
+import static android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS;
+import static android.Manifest.permission.STATUS_BAR_SERVICE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
 import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE;
 import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.SHOW_IMMEDIATELY;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
 import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP;
 import static android.app.Notification.EXTRA_PICTURE;
 import static android.app.Notification.EXTRA_PICTURE_ICON;
@@ -60,6 +63,8 @@
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.PendingIntent.FLAG_MUTABLE;
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
+import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
+import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.PackageManager.FEATURE_TELECOM;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -547,6 +552,7 @@
         mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
         mContext.addMockSystemService(NotificationManager.class, mMockNm);
 
+        doNothing().when(mContext).sendBroadcast(any(), anyString());
         doNothing().when(mContext).sendBroadcastAsUser(any(), any());
         doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
 
@@ -909,7 +915,9 @@
     }
     private ApplicationInfo getApplicationInfo(String pkg, int uid) {
         final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.packageName = pkg;
         applicationInfo.uid = uid;
+        applicationInfo.sourceDir = mContext.getApplicationInfo().sourceDir;
         switch (pkg) {
             case PKG_N_MR1:
                 applicationInfo.targetSdkVersion = Build.VERSION_CODES.N_MR1;
@@ -5535,15 +5543,6 @@
 
     @Test
     public void testBumpFGImportance_channelChangePreOApp() throws Exception {
-        String preOPkg = PKG_N_MR1;
-        final ApplicationInfo legacy = new ApplicationInfo();
-        legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
-        when(mPackageManagerClient.getApplicationInfoAsUser(eq(preOPkg), anyInt(), anyInt()))
-                .thenReturn(legacy);
-        when(mPackageManagerClient.getPackageUidAsUser(eq(preOPkg), anyInt()))
-                .thenReturn(Binder.getCallingUid());
-        getContext().setMockPackageManager(mPackageManagerClient);
-
         Notification.Builder nb = new Notification.Builder(mContext,
                 NotificationChannel.DEFAULT_CHANNEL_ID)
                 .setContentTitle("foo")
@@ -5551,7 +5550,7 @@
                 .setFlag(FLAG_FOREGROUND_SERVICE, true)
                 .setPriority(Notification.PRIORITY_MIN);
 
-        StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9,
+        StatusBarNotification sbn = new StatusBarNotification(PKG_N_MR1, PKG_N_MR1, 9,
                 "testBumpFGImportance_channelChangePreOApp",
                 Binder.getCallingUid(), 0, nb.build(),
                 UserHandle.getUserHandleForUid(Binder.getCallingUid()), null, 0);
@@ -5571,11 +5570,11 @@
                 .setFlag(FLAG_FOREGROUND_SERVICE, true)
                 .setPriority(Notification.PRIORITY_MIN);
 
-        sbn = new StatusBarNotification(preOPkg, preOPkg, 9,
+        sbn = new StatusBarNotification(PKG_N_MR1, PKG_N_MR1, 9,
                 "testBumpFGImportance_channelChangePreOApp", Binder.getCallingUid(),
                 0, nb.build(), UserHandle.getUserHandleForUid(Binder.getCallingUid()), null, 0);
 
-        mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg,
+        mBinderService.enqueueNotificationWithTag(PKG_N_MR1, PKG_N_MR1,
                 "testBumpFGImportance_channelChangePreOApp",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
@@ -5583,7 +5582,7 @@
                 mService.getNotificationRecord(sbn.getKey()).getImportance());
 
         NotificationChannel defaultChannel = mBinderService.getNotificationChannel(
-                preOPkg, mContext.getUserId(), preOPkg, NotificationChannel.DEFAULT_CHANNEL_ID);
+                PKG_N_MR1, mContext.getUserId(), PKG_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID);
         assertEquals(IMPORTANCE_LOW, defaultChannel.getImportance());
     }
 
@@ -14059,6 +14058,22 @@
                 any(), any());
     }
 
+    @Test
+    @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+    public void testSetPrivateNotificationsAllowed() throws Exception {
+        when(mContext.checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS))
+                .thenReturn(PERMISSION_GRANTED);
+        mBinderService.setPrivateNotificationsAllowed(false);
+        Intent expected = new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED)
+                .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, false);
+        ArgumentCaptor<Intent> actual = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcast(actual.capture(), eq(STATUS_BAR_SERVICE));
+
+        assertEquals(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED, actual.getValue().getAction());
+        assertFalse(actual.getValue().getBooleanExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, true));
+        assertFalse(mBinderService.getPrivateNotificationsAllowed());
+    }
+
     private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName)
             throws RemoteException {
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0,