Merge "Update SelectorWithWidgetPreference layout" into main
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 719e438..1b71e73 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -25,6 +25,9 @@
 import static android.app.admin.DevicePolicyResources.UNDEFINED;
 import static android.graphics.drawable.Icon.TYPE_URI;
 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
+import static android.util.TypedValue.COMPLEX_UNIT_PX;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 import static java.util.Objects.requireNonNull;
 
@@ -6001,6 +6004,8 @@
                 contentView.setViewVisibility(p.mTextViewId, View.GONE);
                 contentView.setTextViewText(p.mTextViewId, null);
             }
+
+            updateExpanderAlignment(contentView, p, hasSecondLine);
             setHeaderlessVerticalMargins(contentView, p, hasSecondLine);
 
             // Update margins to leave space for the top line (but not for headerless views like
@@ -6010,12 +6015,29 @@
                 int margin = getContentMarginTop(mContext,
                         R.dimen.notification_2025_content_margin_top);
                 contentView.setViewLayoutMargin(R.id.notification_main_column,
-                        RemoteViews.MARGIN_TOP, margin, TypedValue.COMPLEX_UNIT_PX);
+                        RemoteViews.MARGIN_TOP, margin, COMPLEX_UNIT_PX);
             }
 
             return contentView;
         }
 
+        private static void updateExpanderAlignment(RemoteViews contentView,
+                StandardTemplateParams p, boolean hasSecondLine) {
+            if (notificationsRedesignTemplates() && p.mHeaderless) {
+                if (!hasSecondLine) {
+                    // If there's no text, let's center the expand button vertically to align things
+                    // more nicely. This is handled separately for notifications that use a
+                    // NotificationHeaderView, see NotificationHeaderView#centerTopLine.
+                    contentView.setViewLayoutHeight(R.id.expand_button, MATCH_PARENT,
+                            COMPLEX_UNIT_PX);
+                } else {
+                    // Otherwise, just use the default height for the button to keep it top-aligned.
+                    contentView.setViewLayoutHeight(R.id.expand_button, WRAP_CONTENT,
+                            COMPLEX_UNIT_PX);
+                }
+            }
+        }
+
         private static void setHeaderlessVerticalMargins(RemoteViews contentView,
                 StandardTemplateParams p, boolean hasSecondLine) {
             if (Flags.notificationsRedesignTemplates() || !p.mHeaderless) {
@@ -9560,7 +9582,7 @@
                 int marginStart = res.getDimensionPixelSize(
                         R.dimen.notification_2025_content_margin_start);
                 contentView.setViewLayoutMargin(R.id.title,
-                        RemoteViews.MARGIN_START, marginStart, TypedValue.COMPLEX_UNIT_PX);
+                        RemoteViews.MARGIN_START, marginStart, COMPLEX_UNIT_PX);
             }
             if (isLegacyHeaderless) {
                 // Collapsed legacy messaging style has a 1-line limit.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index f58baff..4fc894c 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -789,6 +789,12 @@
             in @nullable ImeTracker.Token statsToken);
 
     /**
+     * Updates the currently animating insets types of a remote process.
+     */
+    @EnforcePermission("MANAGE_APP_TOKENS")
+    void updateDisplayWindowAnimatingTypes(int displayId, int animatingTypes);
+
+    /**
      * Called to get the expected window insets.
      *
      * @return {@code true} if system bars are always consumed.
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 1f8f0820..7d6d5a2 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -272,6 +272,15 @@
             in @nullable ImeTracker.Token imeStatsToken);
 
     /**
+     * Notifies WindowState what insets types are currently running within the Window.
+     * see {@link com.android.server.wm.WindowState#mInsetsAnimationRunning).
+     *
+     * @param window The window that is insets animaiton is running.
+     * @param animatingTypes Indicates the currently animating insets types.
+     */
+    oneway void updateAnimatingTypes(IWindow window, int animatingTypes);
+
+    /**
      * Called when the system gesture exclusion has changed.
      */
     oneway void reportSystemGestureExclusionChanged(IWindow window, in List<Rect> exclusionRects);
@@ -372,14 +381,4 @@
      */
     oneway void notifyImeWindowVisibilityChangedFromClient(IWindow window, boolean visible,
             in ImeTracker.Token statsToken);
-
-    /**
-     * Notifies WindowState whether inset animations are currently running within the Window.
-     * This value is used by the server to vote for refresh rate.
-     * see {@link com.android.server.wm.WindowState#mInsetsAnimationRunning).
-     *
-     * @param window The window that is insets animaiton is running.
-     * @param running Indicates the insets animation state.
-     */
-    oneway void notifyInsetsAnimationRunningStateChanged(IWindow window, boolean running);
 }
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 0d82acd..462c5c6 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -211,12 +211,12 @@
         }
 
         /**
-         * Notifies when the state of running animation is changed. The state is either "running" or
-         * "idle".
+         * Notifies when the insets types of running animation have changed. The animatingTypes
+         * contain all types, which have an ongoing animation.
          *
-         * @param running {@code true} if there is any animation running; {@code false} otherwise.
+         * @param animatingTypes the {@link InsetsType}s that are currently animating
          */
-        default void notifyAnimationRunningStateChanged(boolean running) {}
+        default void updateAnimatingTypes(@InsetsType int animatingTypes) {}
 
         /** @see ViewRootImpl#isHandlingPointerEvent */
         default boolean isHandlingPointerEvent() {
@@ -665,6 +665,9 @@
     /** Set of inset types which are requested visible which are reported to the host */
     private @InsetsType int mReportedRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
 
+    /** Set of insets types which are currently animating */
+    private @InsetsType int mAnimatingTypes = 0;
+
     /** Set of inset types that we have controls of */
     private @InsetsType int mControllableTypes;
 
@@ -745,9 +748,10 @@
                             mFrame, mFromState, mToState, RESIZE_INTERPOLATOR,
                             ANIMATION_DURATION_RESIZE, mTypes, InsetsController.this);
                     if (mRunningAnimations.isEmpty()) {
-                        mHost.notifyAnimationRunningStateChanged(true);
+                        mHost.updateAnimatingTypes(runner.getTypes());
                     }
                     mRunningAnimations.add(new RunningAnimation(runner, runner.getAnimationType()));
+                    mAnimatingTypes |= runner.getTypes();
                 }
             };
 
@@ -1564,9 +1568,8 @@
             }
         }
         ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING);
-        if (mRunningAnimations.isEmpty()) {
-            mHost.notifyAnimationRunningStateChanged(true);
-        }
+        mAnimatingTypes |= runner.getTypes();
+        mHost.updateAnimatingTypes(mAnimatingTypes);
         mRunningAnimations.add(new RunningAnimation(runner, animationType));
         if (DEBUG) Log.d(TAG, "Animation added to runner. useInsetsAnimationThread: "
                 + useInsetsAnimationThread);
@@ -1827,7 +1830,7 @@
                     dispatchAnimationEnd(runningAnimation.runner.getAnimation());
                 } else {
                     if (Flags.refactorInsetsController()) {
-                        if (removedTypes == ime()
+                        if ((removedTypes & ime()) != 0
                                 && control.getAnimationType() == ANIMATION_TYPE_HIDE) {
                             if (mHost != null) {
                                 // if the (hide) animation is cancelled, the
@@ -1842,9 +1845,11 @@
                 break;
             }
         }
-        if (mRunningAnimations.isEmpty()) {
-            mHost.notifyAnimationRunningStateChanged(false);
+        if (removedTypes > 0) {
+            mAnimatingTypes &= ~removedTypes;
+            mHost.updateAnimatingTypes(mAnimatingTypes);
         }
+
         onAnimationStateChanged(removedTypes, false /* running */);
     }
 
@@ -1969,14 +1974,6 @@
         return animatingTypes;
     }
 
-    private @InsetsType int computeAnimatingTypes() {
-        int animatingTypes = 0;
-        for (int i = 0; i < mRunningAnimations.size(); i++) {
-            animatingTypes |= mRunningAnimations.get(i).runner.getTypes();
-        }
-        return animatingTypes;
-    }
-
     /**
      * Called when finishing setting requested visible types or finishing setting controls.
      *
@@ -1989,7 +1986,7 @@
             // report its requested visibility at the end of the animation, otherwise we would
             // lose the leash, and it would disappear during the animation
             // TODO(b/326377046) revisit this part and see if we can make it more general
-            typesToReport = mRequestedVisibleTypes | (computeAnimatingTypes() & ime());
+            typesToReport = mRequestedVisibleTypes | (mAnimatingTypes & ime());
         } else {
             typesToReport = mRequestedVisibleTypes;
         }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9498407..7fd7be8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2540,11 +2540,12 @@
     }
 
     /**
-     * Notify the when the running state of a insets animation changed.
+     * Notify the when the animating insets types have changed.
      */
     @VisibleForTesting
-    public void notifyInsetsAnimationRunningStateChanged(boolean running) {
+    public void updateAnimatingTypes(@InsetsType int animatingTypes) {
         if (sToolkitSetFrameRateReadOnlyFlagValue) {
+            boolean running = animatingTypes != 0;
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                 Trace.instant(Trace.TRACE_TAG_VIEW,
                         TextUtils.formatSimple("notifyInsetsAnimationRunningStateChanged(%s)",
@@ -2552,7 +2553,7 @@
             }
             mInsetsAnimationRunning = running;
             try {
-                mWindowSession.notifyInsetsAnimationRunningStateChanged(mWindow, running);
+                mWindowSession.updateAnimatingTypes(mWindow, animatingTypes);
             } catch (RemoteException e) {
             }
         }
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index 889acca4..8954df6 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -171,6 +171,13 @@
     }
 
     @Override
+    public void updateAnimatingTypes(@WindowInsets.Type.InsetsType int animatingTypes) {
+        if (mViewRoot != null) {
+            mViewRoot.updateAnimatingTypes(animatingTypes);
+        }
+    }
+
+    @Override
     public boolean hasAnimationCallbacks() {
         if (mViewRoot.mView == null) {
             return false;
@@ -275,13 +282,6 @@
     }
 
     @Override
-    public void notifyAnimationRunningStateChanged(boolean running) {
-        if (mViewRoot != null) {
-            mViewRoot.notifyInsetsAnimationRunningStateChanged(running);
-        }
-    }
-
-    @Override
     public boolean isHandlingPointerEvent() {
         return mViewRoot != null && mViewRoot.isHandlingPointerEvent();
     }
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 72a595d..0a86ff8 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -597,6 +597,11 @@
     }
 
     @Override
+    public void updateAnimatingTypes(IWindow window, @InsetsType int animatingTypes) {
+        // NO-OP
+    }
+
+    @Override
     public void reportSystemGestureExclusionChanged(android.view.IWindow window,
             List<Rect> exclusionRects) {
     }
@@ -679,11 +684,6 @@
             @NonNull ImeTracker.Token statsToken) {
     }
 
-    @Override
-    public void notifyInsetsAnimationRunningStateChanged(IWindow window, boolean running) {
-        // NO-OP
-    }
-
     void setParentInterface(@Nullable ISurfaceControlViewHostParent parentInterface) {
         IBinder oldInterface = mParentInterface == null ? null : mParentInterface.asBinder();
         IBinder newInterface = parentInterface == null ? null : parentInterface.asBinder();
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 16f4114..c81c2bb 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -196,3 +196,10 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "report_animating_insets_types"
+  namespace: "input_method"
+  description: "Adding animating insets types and report IME visibility at the beginning of hiding"
+  bug: "393049691"
+}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 99fe0cb..5e828ba 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5211,7 +5211,11 @@
      */
     @Nullable
     public String getFontVariationSettings() {
-        return mTextPaint.getFontVariationSettings();
+        if (Flags.typefaceRedesignReadonly()) {
+            return mTextPaint.getFontVariationOverride();
+        } else {
+            return mTextPaint.getFontVariationSettings();
+        }
     }
 
     /**
@@ -5567,10 +5571,10 @@
                             Math.clamp(400 + mFontWeightAdjustment,
                                     FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX)));
                 }
-                mTextPaint.setFontVariationSettings(
+                mTextPaint.setFontVariationOverride(
                         FontVariationAxis.toFontVariationSettings(axes));
             } else {
-                mTextPaint.setFontVariationSettings(fontVariationSettings);
+                mTextPaint.setFontVariationOverride(fontVariationSettings);
             }
             effective = true;
         } else {
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 85794d4..056a0e8 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -181,6 +181,11 @@
      */
     public interface BatteryHistoryStore {
         /**
+         * Returns the maximum amount of storage that can be occupied by the battery history story.
+         */
+        int getMaxHistorySize();
+
+        /**
          * Returns the table of contents, in the chronological order.
          */
         List<BatteryHistoryFragment> getFragments();
@@ -516,6 +521,22 @@
     }
 
     /**
+     * Returns a high estimate of how many items are currently included in the battery history.
+     */
+    public int getEstimatedItemCount() {
+        int estimatedBytes = mHistoryBuffer.dataSize();
+        if (mStore != null) {
+            estimatedBytes += mStore.getMaxHistorySize() * 10;  // account for the compression ratio
+        }
+        if (mHistoryParcels != null) {
+            for (int i = mHistoryParcels.size() - 1; i >= 0; i--) {
+                estimatedBytes += mHistoryParcels.get(i).dataSize();
+            }
+        }
+        return estimatedBytes / 4;    // Minimum size of a history item is 4 bytes
+    }
+
+    /**
      * Creates a read-only copy of the battery history.  Does not copy the files stored
      * in the system directory, so it is not safe while actively writing history.
      */
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index 0d5d876..38398b4 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -47,6 +47,8 @@
     private boolean mClosed;
     private long mBaseMonotonicTime;
     private long mBaseTimeUtc;
+    private int mItemIndex = 0;
+    private int mMaxHistoryItems;
 
     public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, long startTimeMs,
             long endTimeMs) {
@@ -54,6 +56,7 @@
         mStartTimeMs = startTimeMs;
         mEndTimeMs = (endTimeMs != MonotonicClock.UNDEFINED) ? endTimeMs : Long.MAX_VALUE;
         mHistoryItem.clear();
+        mMaxHistoryItems = history.getEstimatedItemCount();
     }
 
     @Override
@@ -80,6 +83,11 @@
 
     private void advance() {
         while (true) {
+            if (mItemIndex > mMaxHistoryItems) {
+                Slog.wtfStack(TAG, "Number of battery history items is too large: " + mItemIndex);
+                break;
+            }
+
             Parcel p = mBatteryStatsHistory.getNextParcel(mStartTimeMs, mEndTimeMs);
             if (p == null) {
                 break;
@@ -109,6 +117,7 @@
                 break;
             }
             if (mHistoryItem.time >= mStartTimeMs) {
+                mItemIndex++;
                 mNextItemReady = true;
                 return;
             }
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index c0fe0d1..3472d68 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -555,6 +555,18 @@
         mNotificationProgressDrawable.setParts(p.first);
         mAdjustedProgressFraction =
                 (p.second - mTrackerDrawWidth / 2F) / (width - mTrackerDrawWidth);
+
+        mNotificationProgressDrawable.updateEndDotColor(getEndDotColor(fallbackSegments));
+    }
+
+    private int getEndDotColor(List<ProgressStyle.Segment> fallbackSegments) {
+        if (!mProgressModel.isStyledByProgress()) return Color.TRANSPARENT;
+        if (mProgressModel.getProgress() == mProgressModel.getProgressMax()) {
+            return Color.TRANSPARENT;
+        }
+
+        return fallbackSegments == null ? mProgressModel.getSegments().getLast().getColor()
+                : fallbackSegments.getLast().getColor();
     }
 
     private void updateTrackerAndBarPos(int w, int h) {
diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
index 30dcc67..b109610 100644
--- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java
+++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
@@ -21,6 +21,7 @@
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
@@ -74,6 +75,8 @@
         mFillPaint.setStyle(Paint.Style.FILL);
     }
 
+    private @ColorInt int mEndDotColor = Color.TRANSPARENT;
+
     private int mAlpha;
 
     public NotificationProgressDrawable() {
@@ -125,6 +128,16 @@
         setParts(Arrays.asList(parts));
     }
 
+    /**
+     * Update the color of the end dot. If TRANSPARENT, the dot is not drawn.
+     */
+    public void updateEndDotColor(@ColorInt int endDotColor) {
+        if (mEndDotColor != endDotColor) {
+            mEndDotColor = endDotColor;
+            invalidateSelf();
+        }
+    }
+
     @Override
     public void draw(@NonNull Canvas canvas) {
         final float pointRadius = mState.mPointRadius;
@@ -164,6 +177,18 @@
                 canvas.drawRoundRect(mPointRectF, cornerRadius, cornerRadius, mFillPaint);
             }
         }
+
+        if (mEndDotColor != Color.TRANSPARENT) {
+            final float right = (float) getBounds().right;
+            final float dotRadius = mState.mFadedSegmentHeight / 2F;
+            mFillPaint.setColor(mEndDotColor);
+            // Use drawRoundRect instead of drawCircle to ensure alignment with the segment below.
+            mSegRectF.set(
+                    Math.round(right - mState.mFadedSegmentHeight), Math.round(centerY - dotRadius),
+                            Math.round(right), Math.round(centerY + dotRadius));
+            canvas.drawRoundRect(mSegRectF, mState.mSegmentCornerRadius,
+                    mState.mSegmentCornerRadius, mFillPaint);
+        }
     }
 
     @Override
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 9acb242..a1961ae 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -268,6 +268,9 @@
      72dp (content margin) - 12dp (action padding) - 4dp (button inset) -->
     <dimen name="notification_2025_actions_margin_start">56dp</dimen>
 
+    <!-- Notification action button text size -->
+    <dimen name="notification_2025_action_text_size">16sp</dimen>
+
     <!-- The margin on the end of most content views (ignores the expander) -->
     <dimen name="notification_content_margin_end">16dp</dimen>
 
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 4516e9c..af87af0 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -1195,6 +1195,23 @@
         });
     }
 
+    @Test
+    public void testAnimatingTypes() throws Exception {
+        prepareControls();
+
+        final int types = navigationBars() | statusBars();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            clearInvocations(mTestHost);
+            mController.hide(types);
+            // quickly jump to final state by cancelling it.
+            mController.cancelExistingAnimations();
+        });
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        verify(mTestHost, times(1)).updateAnimatingTypes(eq(types));
+        verify(mTestHost, times(1)).updateAnimatingTypes(eq(0) /* animatingTypes */);
+    }
+
     private void waitUntilNextFrame() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         Choreographer.getMainThreadInstance().postCallback(Choreographer.CALLBACK_COMMIT,
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index c40137f..f5d1e7a 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -1054,7 +1054,7 @@
         ViewRootImpl viewRootImpl = mView.getViewRootImpl();
         sInstrumentation.runOnMainSync(() -> {
             mView.invalidate();
-            viewRootImpl.notifyInsetsAnimationRunningStateChanged(true);
+            viewRootImpl.updateAnimatingTypes(Type.systemBars());
             mView.invalidate();
         });
         sInstrumentation.waitForIdleSync();
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index d754243..8d18f95 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -68,4 +68,8 @@
     <color name="desktop_mode_caption_button_on_hover_light">#11000000</color>
     <color name="desktop_mode_caption_button_on_hover_dark">#11FFFFFF</color>
     <color name="desktop_mode_caption_button">#00000000</color>
+    <color name="tiling_divider_background_light">#C9C7B6</color>
+    <color name="tiling_divider_background_dark">#4A4739</color>
+    <color name="tiling_handle_background_light">#000000</color>
+    <color name="tiling_handle_background_dark">#FFFFFF</color>
 </resources>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 7acad50..2e33253 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -22,6 +22,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.TaskInfo;
 import android.content.Context;
 import android.hardware.display.DisplayManager;
 import android.os.SystemProperties;
@@ -287,6 +288,16 @@
     }
 
     /**
+     * @return If {@code true} we set opaque background for all freeform tasks to prevent freeform
+     * tasks below from being visible if freeform task window above is translucent.
+     * Otherwise if fluid resize is enabled, add a background to freeform tasks.
+     */
+    public static boolean shouldSetBackground(@NonNull TaskInfo taskInfo) {
+        return taskInfo.isFreeform() && (!DesktopModeStatus.isVeiledResizeEnabled()
+                || DesktopModeFlags.ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS.isTrue());
+    }
+
+    /**
      * @return {@code true} if the app handle should be shown because desktop mode is enabled or
      * the device has a large screen
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index df82091..dd2050a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -461,6 +461,14 @@
             }
         }
 
+        private void setAnimating(boolean imeAnimationOngoing) {
+            int animatingTypes = imeAnimationOngoing ? WindowInsets.Type.ime() : 0;
+            try {
+                mWmService.updateDisplayWindowAnimatingTypes(mDisplayId, animatingTypes);
+            } catch (RemoteException e) {
+            }
+        }
+
         private int imeTop(float surfaceOffset, float surfacePositionY) {
             // surfaceOffset is already offset by the surface's top inset, so we need to subtract
             // the top inset so that the return value is in screen coordinates.
@@ -619,6 +627,9 @@
                                 + imeTop(hiddenY, defaultY) + "->" + imeTop(shownY, defaultY)
                                 + " showing:" + (mAnimationDirection == DIRECTION_SHOW));
                     }
+                    if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
+                        setAnimating(true);
+                    }
                     int flags = dispatchStartPositioning(mDisplayId, imeTop(hiddenY, defaultY),
                             imeTop(shownY, defaultY), mAnimationDirection == DIRECTION_SHOW,
                             isFloating, t);
@@ -666,6 +677,8 @@
                     }
                     if (!android.view.inputmethod.Flags.refactorInsetsController()) {
                         dispatchEndPositioning(mDisplayId, mCancelled, t);
+                    } else if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
+                        setAnimating(false);
                     }
                     if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
                         ImeTracker.forLogging().onProgress(mStatsToken,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
index 6c04e2a..d304e20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
@@ -80,7 +80,19 @@
     private int mHoveringWidth;
     private int mHoveringHeight;
     private boolean mIsLeftRightSplit;
+    private boolean mIsSplitScreen;
 
+    /**
+     * Notifies the divider of ui mode change.
+     *
+     * @param isDarkMode Whether the mode is ui dark mode.
+     */
+    public void onUiModeChange(boolean isDarkMode) {
+        if (!mIsSplitScreen) {
+            mPaint.setColor(getTilingHandleColor(isDarkMode));
+            invalidate();
+        }
+    }
     public DividerHandleView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         mPaint.setColor(getResources().getColor(R.color.docked_divider_handle, null));
@@ -103,6 +115,27 @@
         mHoveringHeight = mHeight > mWidth ? ((int) (mHeight * 1.5f)) : mHeight;
     }
 
+    /**
+     * Used by tiling infrastructure to specify display light/dark mode and
+     * whether handle colors should be overridden on display mode change in case
+     * of non split screen.
+     * @param isSplitScreen Whether the divider is used by split screen or tiling.
+     * @param isDarkMode Whether the mode is ui dark mode.
+     */
+    public void setup(boolean isSplitScreen, boolean isDarkMode) {
+        mIsSplitScreen = isSplitScreen;
+        if (!mIsSplitScreen) {
+            mPaint.setColor(getTilingHandleColor(isDarkMode));
+            setAlpha(.9f);
+        }
+    }
+
+    private int getTilingHandleColor(Boolean isDarkMode) {
+        return isDarkMode ? getResources().getColor(
+                R.color.tiling_handle_background_dark, null /* theme */) : getResources().getColor(
+                R.color.tiling_handle_background_light, null /* theme */);
+    }
+
     /** sets whether it's a left/right or top/bottom split */
     public void setIsLeftRightSplit(boolean isLeftRightSplit) {
         mIsLeftRightSplit = isLeftRightSplit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
index d5aaf75..cf0ecae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
@@ -47,15 +47,17 @@
     private InvertedRoundedCornerDrawInfo mBottomLeftCorner;
     private InvertedRoundedCornerDrawInfo mBottomRightCorner;
     private boolean mIsLeftRightSplit;
+    private boolean mIsSplitScreen;
 
     public DividerRoundedCorner(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         mDividerWidth = getResources().getDimensionPixelSize(R.dimen.split_divider_bar_width);
         mDividerBarBackground = new Paint();
         mDividerBarBackground.setColor(
-                getResources().getColor(R.color.split_divider_background, null));
+                getResources().getColor(R.color.split_divider_background, null /* theme */));
         mDividerBarBackground.setFlags(Paint.ANTI_ALIAS_FLAG);
         mDividerBarBackground.setStyle(Paint.Style.FILL);
+        mIsSplitScreen = false;
     }
 
     @Override
@@ -99,7 +101,41 @@
     }
 
     /**
+     * Used by tiling infrastructure to specify display light/dark mode and
+     * whether handle colors should be overridden on display mode change in case
+     * of non split screen.
+     *
+     * @param isSplitScreen Whether the divider is used by split screen or tiling.
+     * @param isDarkMode    Whether the mode is ui dark mode.
+     */
+    public void setup(boolean isSplitScreen, boolean isDarkMode) {
+        mIsSplitScreen = isSplitScreen;
+        if (!isSplitScreen) {
+            mDividerBarBackground.setColor(getTilingHandleColor(isDarkMode));
+        }
+    }
+
+    /**
+     * Notifies the divider of ui mode change.
+     *
+     * @param isDarkMode Whether the mode is ui dark mode.
+     */
+    public void onUiModeChange(boolean isDarkMode) {
+        if (!mIsSplitScreen) {
+            mDividerBarBackground.setColor(getTilingHandleColor(isDarkMode));
+            invalidate();
+        }
+    }
+
+    private int getTilingHandleColor(boolean isDarkMode) {
+        return isDarkMode ? getResources().getColor(
+                R.color.tiling_divider_background_dark, null /* theme */) : getResources().getColor(
+                R.color.tiling_divider_background_light, null /* theme */);
+    }
+
+    /**
      * Set whether the rounded corner is for a left/right split.
+     *
      * @param isLeftRightSplit whether it's a left/right split or top/bottom split.
      */
     public void setIsLeftRightSplit(boolean isLeftRightSplit) {
@@ -123,7 +159,16 @@
             mCornerPosition = cornerPosition;
 
             final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(cornerPosition);
-            mRadius = roundedCorner == null ? 0 : roundedCorner.getRadius();
+            if (mIsSplitScreen) {
+                mRadius = roundedCorner == null ? 0 : roundedCorner.getRadius();
+            } else {
+                mRadius = mContext
+                        .getResources()
+                        .getDimensionPixelSize(
+                                com.android.wm.shell.shared.R.dimen
+                                        .desktop_windowing_freeform_rounded_corner_radius);
+            }
+
 
             // Starts with a filled square, and then subtracting out a circle from the appropriate
             // corner.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 49510c8..5e8c1fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -61,6 +61,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
 import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
@@ -247,6 +248,10 @@
         relayoutParams.mOccludingCaptionElements.add(controlsElement);
         relayoutParams.mCaptionTopPadding = getTopPadding(relayoutParams,
                 taskInfo.getConfiguration().windowConfiguration.getBounds(), displayInsetsState);
+        // Set opaque background for all freeform tasks to prevent freeform tasks below
+        // from being visible if freeform task window above is translucent.
+        // Otherwise if fluid resize is enabled, add a background to freeform tasks.
+        relayoutParams.mShouldSetBackground = DesktopModeStatus.shouldSetBackground(taskInfo);
     }
 
     @SuppressLint("MissingPermission")
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 0d773ec..7ef1a93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -558,6 +558,7 @@
         } else {
             decoration.relayout(taskInfo, taskInfo.isFocused, decoration.mExclusionRegion);
         }
+        mDesktopTilingDecorViewModel.onTaskInfoChange(taskInfo);
         mActivityOrientationChangeHandler.ifPresent(handler ->
                 handler.handleActivityOrientationChange(oldTaskInfo, taskInfo));
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 6165dbf..30e5c2a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -1064,6 +1064,10 @@
             relayoutParams.mCornerRadius = shouldIgnoreCornerRadius ? INVALID_CORNER_RADIUS :
                     getCornerRadius(context, relayoutParams.mLayoutResId);
         }
+        // Set opaque background for all freeform tasks to prevent freeform tasks below
+        // from being visible if freeform task window above is translucent.
+        // Otherwise if fluid resize is enabled, add a background to freeform tasks.
+        relayoutParams.mShouldSetBackground = DesktopModeStatus.shouldSetBackground(taskInfo);
     }
 
     private static int getCornerRadius(@NonNull Context context, int layoutResId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 4002dc5..7baef2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.windowdecor;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
 import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.mandatorySystemGestures;
@@ -57,7 +56,6 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
-import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
 import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
@@ -504,15 +502,14 @@
             startT.show(mTaskSurface);
         }
 
-        if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
-                && !DesktopModeStatus.isVeiledResizeEnabled()) {
-            // When fluid resize is enabled, add a background to freeform tasks
-            int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
+        if (params.mShouldSetBackground) {
+            final int backgroundColorInt = mTaskInfo.taskDescription != null
+                    ? mTaskInfo.taskDescription.getBackgroundColor() : Color.BLACK;
             mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
             mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
             mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
             startT.setColor(mTaskSurface, mTmpColor);
-        } else if (!DesktopModeStatus.isVeiledResizeEnabled()) {
+        } else {
             startT.unsetColor(mTaskSurface);
         }
 
@@ -833,6 +830,7 @@
         boolean mSetTaskVisibilityPositionAndCrop;
         boolean mHasGlobalFocus;
         boolean mShouldSetAppBounds;
+        boolean mShouldSetBackground;
 
         void reset() {
             mLayoutResId = Resources.ID_NULL;
@@ -857,6 +855,7 @@
             mAsyncViewHost = false;
             mHasGlobalFocus = false;
             mShouldSetAppBounds = false;
+            mShouldSetBackground = false;
         }
 
         boolean hasInputFeatureSpy() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
index ee5d0e8..e9426d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
@@ -137,6 +137,10 @@
         }
     }
 
+    fun onTaskInfoChange(taskInfo: RunningTaskInfo) {
+        tilingTransitionHandlerByDisplayId.get(taskInfo.displayId)?.onTaskInfoChange(taskInfo)
+    }
+
     override fun onDisplayChange(
         displayId: Int,
         fromRotation: Int,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
index fbbf1a5..cb45c17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
@@ -57,6 +57,7 @@
     private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
     private var dividerBounds: Rect,
     private val displayContext: Context,
+    private val isDarkMode: Boolean,
 ) : WindowlessWindowManager(config, leash, null), DividerMoveCallback, View.OnLayoutChangeListener {
     private lateinit var viewHost: SurfaceControlViewHost
     private var tilingDividerView: TilingDividerView? = null
@@ -153,7 +154,7 @@
         surfaceControlViewHost.setView(dividerView, lp)
         val tmpDividerBounds = Rect()
         getDividerBounds(tmpDividerBounds)
-        dividerView.setup(this, tmpDividerBounds, handleRegionSize)
+        dividerView.setup(this, tmpDividerBounds, handleRegionSize, isDarkMode)
         t.setRelativeLayer(leash, relativeLeash, 1)
             .setPosition(
                 leash,
@@ -172,6 +173,11 @@
         updateTouchRegion()
     }
 
+    /** Changes divider colour if dark/light mode is toggled. */
+    fun onUiModeChange(isDarkMode: Boolean) {
+        tilingDividerView?.onUiModeChange(isDarkMode)
+    }
+
     /** Hides the divider bar. */
     fun hideDividerBar() {
         if (!dividerShown) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index 9833325..a45df04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -103,6 +103,7 @@
     @VisibleForTesting
     var desktopTilingDividerWindowManager: DesktopTilingDividerWindowManager? = null
     private lateinit var dividerBounds: Rect
+    private var isDarkMode = false
     private var isResizing = false
     private var isTilingFocused = false
 
@@ -129,6 +130,7 @@
         val isTiled = destinationBounds != taskInfo.configuration.windowConfiguration.bounds
 
         initTilingApps(resizeMetadata, position, taskInfo)
+        isDarkMode = isTaskInDarkMode(taskInfo)
         // Observe drag resizing to break tiling if a task is drag resized.
         desktopModeWindowDecoration.addDragResizeListener(this)
 
@@ -232,6 +234,7 @@
                     transactionSupplier,
                     dividerBounds,
                     displayContext,
+                    isDarkMode,
                 )
             }
         // a leash to present the divider on top of, without re-parenting.
@@ -356,6 +359,17 @@
         transitions.startTransition(TRANSIT_CHANGE, wct, this)
     }
 
+    fun onTaskInfoChange(taskInfo: RunningTaskInfo) {
+        val isCurrentTaskInDarkMode = isTaskInDarkMode(taskInfo)
+        if (isCurrentTaskInDarkMode == isDarkMode || !isTilingManagerInitialised) return
+        isDarkMode = isCurrentTaskInDarkMode
+        desktopTilingDividerWindowManager?.onUiModeChange(isDarkMode)
+    }
+
+    fun isTaskInDarkMode(taskInfo: RunningTaskInfo): Boolean =
+        (taskInfo.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
+            Configuration.UI_MODE_NIGHT_YES
+
     override fun startAnimation(
         transition: IBinder,
         info: TransitionInfo,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
index b8e3b0f..54dcd2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
@@ -16,6 +16,7 @@
 package com.android.wm.shell.windowdecor.tiling
 
 import android.content.Context
+import android.content.res.Configuration
 import android.graphics.Canvas
 import android.graphics.Paint
 import android.graphics.Rect
@@ -85,11 +86,14 @@
         dividerMoveCallback: DividerMoveCallback,
         dividerBounds: Rect,
         handleRegionSize: Size,
+        isDarkMode: Boolean,
     ) {
         callback = dividerMoveCallback
         this.dividerBounds.set(dividerBounds)
         handle.setIsLeftRightSplit(true)
+        handle.setup(/* isSplitScreen= */ false, isDarkMode)
         corners.setIsLeftRightSplit(true)
+        corners.setup(/* isSplitScreen= */ false, isDarkMode)
         handleRegionHeight = handleRegionSize.height
         handleRegionWidth = handleRegionSize.width
         cornersRadius =
@@ -103,6 +107,18 @@
             )
     }
 
+    fun onUiModeChange(isDarkMode: Boolean) {
+        handle.onUiModeChange(isDarkMode)
+        corners.onUiModeChange(isDarkMode)
+        paint.color =
+            if (isDarkMode) {
+                resources.getColor(R.color.tiling_divider_background_dark, null /* theme */)
+            } else {
+                resources.getColor(R.color.tiling_divider_background_light, null /* theme */)
+            }
+        invalidate()
+    }
+
     override fun onFinishInflate() {
         super.onFinishInflate()
         dividerBar = requireViewById(R.id.divider_bar)
@@ -112,7 +128,15 @@
             resources.getDimensionPixelSize(R.dimen.docked_stack_divider_lift_elevation)
         setOnTouchListener(this)
         setWillNotDraw(false)
-        paint.color = resources.getColor(R.color.split_divider_background, null)
+        paint.color =
+            if (
+                context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
+                    Configuration.UI_MODE_NIGHT_YES
+            ) {
+                resources.getColor(R.color.tiling_divider_background_dark, /* theme= */null)
+            } else {
+                resources.getColor(R.color.tiling_divider_background_light, /* theme= */ null)
+            }
         paint.isAntiAlias = true
         paint.style = Paint.Style.FILL
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
index 4082ffd..fb62ba7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.shared.desktopmode
 
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.content.Context
 import android.content.res.Resources
 import android.platform.test.annotations.DisableFlags
@@ -27,6 +28,7 @@
 import com.android.internal.R
 import com.android.window.flags.Flags
 import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.util.createTaskInfo
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -152,6 +154,70 @@
         assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
     }
 
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+        Flags.FLAG_ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS,
+    )
+    @Test
+    fun shouldSetBackground_BTWFlagEnabled_freeformTask_returnsTrue() {
+        val freeFormTaskInfo = createTaskInfo(deviceWindowingMode = WINDOWING_MODE_FREEFORM)
+        assertThat(DesktopModeStatus.shouldSetBackground(freeFormTaskInfo)).isTrue()
+    }
+
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+        Flags.FLAG_ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS,
+    )
+    @Test
+    fun shouldSetBackground_BTWFlagEnabled_notFreeformTask_returnsFalse() {
+        val notFreeFormTaskInfo = createTaskInfo()
+        assertThat(DesktopModeStatus.shouldSetBackground(notFreeFormTaskInfo)).isFalse()
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @DisableFlags(Flags.FLAG_ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS)
+    @Test
+    fun shouldSetBackground_BTWFlagDisabled_freeformTaskAndFluid_returnsTrue() {
+        val freeFormTaskInfo = createTaskInfo(deviceWindowingMode = WINDOWING_MODE_FREEFORM)
+
+        setIsVeiledResizeEnabled(false)
+        assertThat(DesktopModeStatus.shouldSetBackground(freeFormTaskInfo)).isTrue()
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @DisableFlags(Flags.FLAG_ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS)
+    @Test
+    fun shouldSetBackground_BTWFlagDisabled_freeformTaskAndVeiled_returnsFalse() {
+        val freeFormTaskInfo = createTaskInfo(deviceWindowingMode = WINDOWING_MODE_FREEFORM)
+
+        setIsVeiledResizeEnabled(true)
+        assertThat(DesktopModeStatus.shouldSetBackground(freeFormTaskInfo)).isFalse()
+    }
+
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+        Flags.FLAG_ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS,
+    )
+    @Test
+    fun shouldSetBackground_BTWFlagEnabled_freeformTaskAndFluid_returnsTrue() {
+        val freeFormTaskInfo = createTaskInfo(deviceWindowingMode = WINDOWING_MODE_FREEFORM)
+
+        setIsVeiledResizeEnabled(false)
+        assertThat(DesktopModeStatus.shouldSetBackground(freeFormTaskInfo)).isTrue()
+    }
+
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+        Flags.FLAG_ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS,
+    )
+    @Test
+    fun shouldSetBackground_BTWFlagEnabled_windowModesTask_freeformTaskAndVeiled_returnsTrue() {
+        val freeFormTaskInfo = createTaskInfo(deviceWindowingMode = WINDOWING_MODE_FREEFORM)
+
+        setIsVeiledResizeEnabled(true)
+        assertThat(DesktopModeStatus.shouldSetBackground(freeFormTaskInfo)).isTrue()
+    }
+
     @Test
     fun isDeviceEligibleForDesktopMode_configDEModeOnAndIntDispHostsDesktop_returnsTrue() {
         doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
@@ -254,4 +320,11 @@
         deviceRestrictions.isAccessible = true
         deviceRestrictions.setBoolean(/* obj= */ null, /* z= */ !eligible)
     }
+
+    private fun setIsVeiledResizeEnabled(enabled: Boolean) {
+        val deviceRestrictions =
+            DesktopModeStatus::class.java.getDeclaredField("IS_VEILED_RESIZE_ENABLED")
+        deviceRestrictions.isAccessible = true
+        deviceRestrictions.setBoolean(/* obj= */ null, /* z= */ enabled)
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 16c7935..e89a122 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -321,6 +321,19 @@
     }
 
     @Test
+    fun testOnTaskInfoChanged_tilingNotified() {
+        val task = createTask(
+            windowingMode = WINDOWING_MODE_FREEFORM
+        )
+        setUpMockDecorationsForTasks(task)
+
+        onTaskOpening(task)
+        desktopModeWindowDecorViewModel.onTaskInfoChanged(task)
+
+        verify(mockTilingWindowDecoration).onTaskInfoChange(task)
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
     fun testInsetsStateChanged_notifiesAllDecorsInDisplay() {
         val task1 = createTask(windowingMode = WINDOWING_MODE_FREEFORM, displayId = 1)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index af01623..a2927fa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -24,7 +24,6 @@
 import static android.view.WindowInsets.Type.mandatorySystemGestures;
 import static android.view.WindowInsets.Type.statusBars;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlBuilder;
 import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
 
@@ -51,7 +50,6 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.mockito.quality.Strictness.LENIENT;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -79,13 +77,11 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
-import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.tests.R;
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
 import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
@@ -564,12 +560,7 @@
     }
 
     @Test
-    public void testRelayout_fluidResizeEnabled_freeformTask_setTaskSurfaceColor() {
-        StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
-                DesktopModeStatus.class).strictness(
-                LENIENT).startMocking();
-        when(DesktopModeStatus.isVeiledResizeEnabled()).thenReturn(false);
-
+    public void testRelayout_shouldSetBackground_freeformTask_setTaskSurfaceColor() {
         final Display defaultDisplay = mock(Display.class);
         doReturn(defaultDisplay).when(mMockDisplayController)
                 .getDisplay(Display.DEFAULT_DISPLAY);
@@ -595,11 +586,10 @@
                 .build();
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
 
+        mRelayoutParams.mShouldSetBackground = true;
         windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
 
         verify(mMockSurfaceControlStartT).setColor(mMockTaskSurface, new float[]{1.f, 1.f, 0.f});
-
-        mockitoSession.finishMocking();
     }
 
     @Test
@@ -627,11 +617,7 @@
     }
 
     @Test
-    public void testRelayout_fluidResizeEnabled_fullscreenTask_clearTaskSurfaceColor() {
-        StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
-                DesktopModeStatus.class).strictness(LENIENT).startMocking();
-        when(DesktopModeStatus.isVeiledResizeEnabled()).thenReturn(false);
-
+    public void testRelayout_shouldNotSetBackground_fullscreenTask_clearTaskSurfaceColor() {
         final Display defaultDisplay = mock(Display.class);
         doReturn(defaultDisplay).when(mMockDisplayController)
                 .getDisplay(Display.DEFAULT_DISPLAY);
@@ -655,12 +641,11 @@
                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
                 .build();
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+        mRelayoutParams.mIsCaptionVisible = false;
 
         windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
 
         verify(mMockSurfaceControlStartT).unsetColor(mMockTaskSurface);
-
-        mockitoSession.finishMocking();
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
index 121e0e9..8442056 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
@@ -81,6 +81,7 @@
                 transactionSupplierMock,
                 BOUNDS,
                 context,
+                /* isDarkMode= */ true
             )
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt
index 9a9d05a..9a3d5d8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt
@@ -60,7 +60,7 @@
         tilingDividerView =
             LayoutInflater.from(mContext).inflate(R.layout.tiling_split_divider, /* root= */ null)
                 as TilingDividerView
-        tilingDividerView.setup(dividerMoveCallbackMock, DIVIDER_BOUNDS, HANDLE_SIZE)
+        tilingDividerView.setup(dividerMoveCallbackMock, DIVIDER_BOUNDS, HANDLE_SIZE, true)
         tilingDividerView.handleY = 0..1500
     }
 
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 38ac8ab..a892e88 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -141,6 +141,7 @@
                 "libsync",
                 "libui",
                 "aconfig_text_flags_c_lib",
+                "aconfig_view_accessibility_flags_c_lib",
                 "server_configurable_flags",
                 "libaconfig_storage_read_api_cc",
                 "libgraphicsenv",
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index 5f84f47..5ceb97c 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -18,6 +18,7 @@
 #define ANDROID_HWUI_FEATURE_FLAGS_H
 
 #ifdef __ANDROID__
+#include <android_view_accessibility.h>
 #include <com_android_text_flags.h>
 #endif  // __ANDROID__
 
@@ -44,6 +45,19 @@
 
 }  // namespace text_feature
 
+namespace view_accessibility_flags {
+
+inline bool force_invert_color() {
+#ifdef __ANDROID__
+    static bool flag = android::view::accessibility::force_invert_color();
+    return flag;
+#else
+    return true;
+#endif  // __ANDROID__
+}
+
+}  // namespace view_accessibility_flags
+
 }  // namespace android
 
 #endif  // ANDROID_HWUI_FEATURE_FLAGS_H
diff --git a/media/java/android/media/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig
index 6d4f0b4..846448b 100644
--- a/media/java/android/media/flags/projection.aconfig
+++ b/media/java/android/media/flags/projection.aconfig
@@ -39,3 +39,12 @@
     }
     is_exported: true
 }
+
+flag {
+    namespace: "media_projection"
+    name: "app_content_sharing"
+    description: "Enable apps to share some sub-surface"
+    bug: "379989921"
+    is_exported: true
+}
+
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt b/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt
index ed144bd..375dade 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt
@@ -78,6 +78,10 @@
     val underSurface: Color,
     val weatherTemp: Color,
     val widgetBackground: Color,
+    val surfaceEffect0: Color,
+    val surfaceEffect1: Color,
+    val surfaceEffect2: Color,
+    val surfaceEffect3: Color,
 ) {
     companion object {
         internal fun color(context: Context, @ColorRes id: Int): Color {
@@ -123,6 +127,10 @@
                 underSurface = color(context, R.color.customColorUnderSurface),
                 weatherTemp = color(context, R.color.customColorWeatherTemp),
                 widgetBackground = color(context, R.color.customColorWidgetBackground),
+                surfaceEffect0 = color(context, R.color.surface_effect_0),
+                surfaceEffect1 = color(context, R.color.surface_effect_1),
+                surfaceEffect2 = color(context, R.color.surface_effect_2),
+                surfaceEffect3 = color(context, R.color.surface_effect_3),
             )
         }
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
index 781e416..ede29d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
@@ -26,6 +26,9 @@
 import com.android.systemui.common.shared.model.PackageInstallSession
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.testKosmos
@@ -173,6 +176,58 @@
         }
 
     @Test
+    fun onCreateUpdatedSession_ignoreNullPackageNameSessions() =
+        kosmos.runTest {
+            val nullPackageSession =
+                SessionInfo().apply {
+                    sessionId = 1
+                    appPackageName = null
+                    appIcon = icon1
+                }
+
+            val wellFormedSession =
+                SessionInfo().apply {
+                    sessionId = 2
+                    appPackageName = "pkg_name"
+                    appIcon = icon2
+                }
+
+            defaultSessions = listOf(wellFormedSession)
+
+            whenever(packageInstaller.allSessions).thenReturn(defaultSessions)
+            whenever(packageInstaller.getSessionInfo(1)).thenReturn(nullPackageSession)
+            whenever(packageInstaller.getSessionInfo(2)).thenReturn(wellFormedSession)
+
+            val packageInstallerMonitor =
+                PackageInstallerMonitor(
+                    handler,
+                    backgroundScope,
+                    logcatLogBuffer("PackageInstallerRepositoryImplTest"),
+                    packageInstaller,
+                )
+
+            val sessions by collectLastValue(packageInstallerMonitor.installSessionsForPrimaryUser)
+
+            // Verify flow updated with the new session
+            assertThat(sessions)
+                .comparingElementsUsing(represents)
+                .containsExactlyElementsIn(defaultSessions)
+
+            val callback =
+                withArgCaptor<PackageInstaller.SessionCallback> {
+                    verify(packageInstaller).registerSessionCallback(capture(), eq(handler))
+                }
+
+            // New session added
+            callback.onCreated(nullPackageSession.sessionId)
+
+            // Verify flow updated with the new session
+            assertThat(sessions)
+                .comparingElementsUsing(represents)
+                .containsExactlyElementsIn(defaultSessions)
+        }
+
+    @Test
     fun installSessions_newSessionsAreAdded() =
         testScope.runTest {
             val installSessions by collectLastValue(underTest.installSessionsForPrimaryUser)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 1899b7d..0e5e333 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -423,5 +423,15 @@
 
         @Override
         public void destroy() {}
+
+        @Override
+        public boolean isDestroyed() {
+            return false;
+        }
+
+        @Override
+        public int getCurrentTileUser() {
+            return 0;
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/TilesAvailabilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/TilesAvailabilityInteractorTest.kt
index 5a58597..67fb100 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/TilesAvailabilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/TilesAvailabilityInteractorTest.kt
@@ -56,166 +56,178 @@
 
     private val createdTiles = mutableListOf<FakeQSTile>()
 
-    private val kosmos = testKosmos().apply {
-        tileAvailabilityInteractorsMap = buildMap {
-            put(AIRPLANE_MODE_TILE_SPEC, QSTileAvailabilityInteractor.AlwaysAvailableInteractor)
-            put(WORK_MODE_TILE_SPEC, FakeTileAvailabilityInteractor(
-                    mapOf(
-                            fakeUserRepository.getSelectedUserInfo().id to flowOf(true),
-                    ).withDefault { flowOf(false) }
-            ))
-            put(HOTSPOT_TILE_SPEC, FakeTileAvailabilityInteractor(
-                    emptyMap<Int, Flow<Boolean>>().withDefault { flowOf(false) }
-            ))
-        }
+    private val kosmos =
+        testKosmos().apply {
+            tileAvailabilityInteractorsMap = buildMap {
+                put(AIRPLANE_MODE_TILE_SPEC, QSTileAvailabilityInteractor.AlwaysAvailableInteractor)
+                put(
+                    WORK_MODE_TILE_SPEC,
+                    FakeTileAvailabilityInteractor(
+                        mapOf(fakeUserRepository.getSelectedUserInfo().id to flowOf(true))
+                            .withDefault { flowOf(false) }
+                    ),
+                )
+                put(
+                    HOTSPOT_TILE_SPEC,
+                    FakeTileAvailabilityInteractor(
+                        emptyMap<Int, Flow<Boolean>>().withDefault { flowOf(false) }
+                    ),
+                )
+            }
 
-        qsTileFactory = constantFactory(
-                tilesForCreator(
+            qsTileFactory =
+                constantFactory(
+                    tilesForCreator(
                         userRepository.getSelectedUserInfo().id,
                         mapOf(
-                                AIRPLANE_MODE_TILE_SPEC to false,
-                                WORK_MODE_TILE_SPEC to false,
-                                HOTSPOT_TILE_SPEC to true,
-                                INTERNET_TILE_SPEC to true,
-                                FLASHLIGHT_TILE_SPEC to false,
-                        )
+                            AIRPLANE_MODE_TILE_SPEC to false,
+                            WORK_MODE_TILE_SPEC to false,
+                            HOTSPOT_TILE_SPEC to true,
+                            INTERNET_TILE_SPEC to true,
+                            FLASHLIGHT_TILE_SPEC to false,
+                        ),
+                    )
                 )
-        )
-    }
+        }
 
     private val underTest by lazy { kosmos.tilesAvailabilityInteractor }
 
     @Test
     @DisableFlags(FLAG_QS_NEW_TILES)
-    fun flagOff_usesAvailabilityFromFactoryTiles() = with(kosmos) {
-        testScope.runTest {
-            val unavailableTiles = underTest.getUnavailableTiles(
-                    setOf(
-                            AIRPLANE_MODE_TILE_SPEC,
-                            WORK_MODE_TILE_SPEC,
-                            HOTSPOT_TILE_SPEC,
-                            INTERNET_TILE_SPEC,
-                            FLASHLIGHT_TILE_SPEC,
-                    ).map(TileSpec::create)
-            )
-            assertThat(unavailableTiles).isEqualTo(setOf(
-                    AIRPLANE_MODE_TILE_SPEC,
-                    WORK_MODE_TILE_SPEC,
-                    FLASHLIGHT_TILE_SPEC,
-            ).mapTo(mutableSetOf(), TileSpec::create))
-        }
-    }
-
-    @Test
-    fun tileCannotBeCreated_isUnavailable() = with(kosmos) {
-        testScope.runTest {
-            val badSpec = TileSpec.create("unknown")
-            val unavailableTiles = underTest.getUnavailableTiles(
-                    setOf(
-                        badSpec
+    fun flagOff_usesAvailabilityFromFactoryTiles() =
+        with(kosmos) {
+            testScope.runTest {
+                val unavailableTiles =
+                    underTest.getUnavailableTiles(
+                        setOf(
+                                AIRPLANE_MODE_TILE_SPEC,
+                                WORK_MODE_TILE_SPEC,
+                                HOTSPOT_TILE_SPEC,
+                                INTERNET_TILE_SPEC,
+                                FLASHLIGHT_TILE_SPEC,
+                            )
+                            .map(TileSpec::create)
                     )
-            )
-            assertThat(unavailableTiles).contains(badSpec)
+                assertThat(unavailableTiles)
+                    .isEqualTo(
+                        setOf(AIRPLANE_MODE_TILE_SPEC, WORK_MODE_TILE_SPEC, FLASHLIGHT_TILE_SPEC)
+                            .mapTo(mutableSetOf(), TileSpec::create)
+                    )
+            }
         }
-    }
+
+    @Test
+    fun tileCannotBeCreated_isUnavailable() =
+        with(kosmos) {
+            testScope.runTest {
+                val badSpec = TileSpec.create("unknown")
+                val unavailableTiles = underTest.getUnavailableTiles(setOf(badSpec))
+                assertThat(unavailableTiles).contains(badSpec)
+            }
+        }
 
     @Test
     @EnableFlags(FLAG_QS_NEW_TILES)
-    fun flagOn_defaultsToInteractorTiles_usesFactoryForOthers() = with(kosmos) {
-        testScope.runTest {
-            val unavailableTiles = underTest.getUnavailableTiles(
+    fun flagOn_defaultsToInteractorTiles_usesFactoryForOthers() =
+        with(kosmos) {
+            testScope.runTest {
+                val unavailableTiles =
+                    underTest.getUnavailableTiles(
+                        setOf(
+                                AIRPLANE_MODE_TILE_SPEC,
+                                WORK_MODE_TILE_SPEC,
+                                HOTSPOT_TILE_SPEC,
+                                INTERNET_TILE_SPEC,
+                                FLASHLIGHT_TILE_SPEC,
+                            )
+                            .map(TileSpec::create)
+                    )
+                assertThat(unavailableTiles)
+                    .isEqualTo(
+                        setOf(HOTSPOT_TILE_SPEC, FLASHLIGHT_TILE_SPEC)
+                            .mapTo(mutableSetOf(), TileSpec::create)
+                    )
+            }
+        }
+
+    @Test
+    @EnableFlags(FLAG_QS_NEW_TILES)
+    fun flagOn_defaultsToInteractorTiles_usesFactoryForOthers_userChange() =
+        with(kosmos) {
+            testScope.runTest {
+                fakeUserRepository.asMainUser()
+                val unavailableTiles =
+                    underTest.getUnavailableTiles(
+                        setOf(
+                                AIRPLANE_MODE_TILE_SPEC,
+                                WORK_MODE_TILE_SPEC,
+                                HOTSPOT_TILE_SPEC,
+                                INTERNET_TILE_SPEC,
+                                FLASHLIGHT_TILE_SPEC,
+                            )
+                            .map(TileSpec::create)
+                    )
+                assertThat(unavailableTiles)
+                    .isEqualTo(
+                        setOf(WORK_MODE_TILE_SPEC, HOTSPOT_TILE_SPEC, FLASHLIGHT_TILE_SPEC)
+                            .mapTo(mutableSetOf(), TileSpec::create)
+                    )
+            }
+        }
+
+    @Test
+    @EnableFlags(FLAG_QS_NEW_TILES)
+    fun flagOn_onlyNeededTilesAreCreated_andThenDestroyed() =
+        with(kosmos) {
+            testScope.runTest {
+                underTest.getUnavailableTiles(
                     setOf(
                             AIRPLANE_MODE_TILE_SPEC,
                             WORK_MODE_TILE_SPEC,
                             HOTSPOT_TILE_SPEC,
                             INTERNET_TILE_SPEC,
                             FLASHLIGHT_TILE_SPEC,
-                    ).map(TileSpec::create)
-            )
-            assertThat(unavailableTiles).isEqualTo(setOf(
-                    HOTSPOT_TILE_SPEC,
-                    FLASHLIGHT_TILE_SPEC,
-            ).mapTo(mutableSetOf(), TileSpec::create))
-        }
-    }
-
-    @Test
-    @EnableFlags(FLAG_QS_NEW_TILES)
-    fun flagOn_defaultsToInteractorTiles_usesFactoryForOthers_userChange() = with(kosmos) {
-        testScope.runTest {
-            fakeUserRepository.asMainUser()
-            val unavailableTiles = underTest.getUnavailableTiles(
-                    setOf(
-                            AIRPLANE_MODE_TILE_SPEC,
-                            WORK_MODE_TILE_SPEC,
-                            HOTSPOT_TILE_SPEC,
-                            INTERNET_TILE_SPEC,
-                            FLASHLIGHT_TILE_SPEC,
-                    ).map(TileSpec::create)
-            )
-            assertThat(unavailableTiles).isEqualTo(setOf(
-                    WORK_MODE_TILE_SPEC,
-                    HOTSPOT_TILE_SPEC,
-                    FLASHLIGHT_TILE_SPEC,
-            ).mapTo(mutableSetOf(), TileSpec::create))
-        }
-    }
-
-    @Test
-    @EnableFlags(FLAG_QS_NEW_TILES)
-    fun flagOn_onlyNeededTilesAreCreated_andThenDestroyed() = with(kosmos) {
-        testScope.runTest {
-            underTest.getUnavailableTiles(
-                    setOf(
-                            AIRPLANE_MODE_TILE_SPEC,
-                            WORK_MODE_TILE_SPEC,
-                            HOTSPOT_TILE_SPEC,
-                            INTERNET_TILE_SPEC,
-                            FLASHLIGHT_TILE_SPEC,
-                    ).map(TileSpec::create)
-            )
-            assertThat(createdTiles.map { it.tileSpec })
+                        )
+                        .map(TileSpec::create)
+                )
+                assertThat(createdTiles.map { it.tileSpec })
                     .containsExactly(INTERNET_TILE_SPEC, FLASHLIGHT_TILE_SPEC)
-            assertThat(createdTiles.all { it.destroyed }).isTrue()
+                assertThat(createdTiles.all { it.isDestroyed }).isTrue()
+            }
         }
-    }
 
     @Test
     @DisableFlags(FLAG_QS_NEW_TILES)
-    fun flagOn_TilesAreCreatedAndThenDestroyed() = with(kosmos) {
-        testScope.runTest {
-            val allTiles = setOf(
-                    AIRPLANE_MODE_TILE_SPEC,
-                    WORK_MODE_TILE_SPEC,
-                    HOTSPOT_TILE_SPEC,
-                    INTERNET_TILE_SPEC,
-                    FLASHLIGHT_TILE_SPEC,
-                )
-            underTest.getUnavailableTiles(allTiles.map(TileSpec::create))
-            assertThat(createdTiles.map { it.tileSpec })
-                    .containsExactlyElementsIn(allTiles)
-            assertThat(createdTiles.all { it.destroyed }).isTrue()
+    fun flagOn_TilesAreCreatedAndThenDestroyed() =
+        with(kosmos) {
+            testScope.runTest {
+                val allTiles =
+                    setOf(
+                        AIRPLANE_MODE_TILE_SPEC,
+                        WORK_MODE_TILE_SPEC,
+                        HOTSPOT_TILE_SPEC,
+                        INTERNET_TILE_SPEC,
+                        FLASHLIGHT_TILE_SPEC,
+                    )
+                underTest.getUnavailableTiles(allTiles.map(TileSpec::create))
+                assertThat(createdTiles.map { it.tileSpec }).containsExactlyElementsIn(allTiles)
+                assertThat(createdTiles.all { it.isDestroyed }).isTrue()
+            }
         }
-    }
-
 
     private fun constantFactory(creatorTiles: Set<FakeQSTile>): QSFactory {
         return FakeQSFactory { spec ->
-            creatorTiles.firstOrNull { it.tileSpec == spec }?.also {
-                createdTiles.add(it)
-            }
+            creatorTiles.firstOrNull { it.tileSpec == spec }?.also { createdTiles.add(it) }
         }
     }
 
     companion object {
         private fun tilesForCreator(
-                user: Int,
-                specAvailabilities: Map<String, Boolean>
+            user: Int,
+            specAvailabilities: Map<String, Boolean>,
         ): Set<FakeQSTile> {
             return specAvailabilities.mapTo(mutableSetOf()) {
-                FakeQSTile(user, it.value).apply {
-                    tileSpec = it.key
-                }
+                FakeQSTile(user, it.value).apply { tileSpec = it.key }
             }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 9b50f1b..c308976 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.qs.pipeline.domain.interactor
 
 import android.content.ComponentName
-import android.content.Context
 import android.content.Intent
 import android.content.pm.UserInfo
 import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
 import android.service.quicksettings.Tile
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -28,653 +28,702 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dump.nano.SystemUIProtoDump
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.qs.QSTile.BooleanState
 import com.android.systemui.plugins.qs.TileDetailsViewModel
 import com.android.systemui.qs.FakeQSFactory
 import com.android.systemui.qs.FakeQSTile
 import com.android.systemui.qs.external.CustomTile
-import com.android.systemui.qs.external.CustomTileStatePersister
 import com.android.systemui.qs.external.TileLifecycleManager
 import com.android.systemui.qs.external.TileServiceKey
-import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
-import com.android.systemui.qs.pipeline.data.repository.FakeCustomTileAddedRepository
-import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
-import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
-import com.android.systemui.qs.pipeline.data.repository.MinimumTilesFixedRepository
-import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.qs.external.customTileStatePersister
+import com.android.systemui.qs.external.tileLifecycleManagerFactory
+import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
 import com.android.systemui.qs.pipeline.domain.model.TileModel
-import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
-import com.android.systemui.qs.tiles.di.NewQSTileFactory
+import com.android.systemui.qs.pipeline.shared.logging.qsLogger
+import com.android.systemui.qs.qsTileFactory
+import com.android.systemui.qs.tiles.di.newQSTileFactory
 import com.android.systemui.qs.toProto
-import com.android.systemui.retail.data.repository.FakeRetailModeRepository
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.data.repository.userRepository
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import com.google.protobuf.nano.MessageNano
-import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyString
-import org.mockito.Mock
 import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableFlags(FLAG_QS_NEW_TILES)
 class CurrentTilesInteractorImplTest : SysuiTestCase() {
 
-    private val tileSpecRepository: TileSpecRepository = FakeTileSpecRepository()
-    private val userRepository = FakeUserRepository()
-    private val installedTilesPackageRepository = FakeInstalledTilesComponentRepository()
-    private val tileFactory = FakeQSFactory(::tileCreator)
-    private val customTileAddedRepository: CustomTileAddedRepository =
-        FakeCustomTileAddedRepository()
-    private val pipelineFlags = QSPipelineFlagsRepository()
-    private val tileLifecycleManagerFactory = TLMFactory()
-    private val minimumTilesRepository = MinimumTilesFixedRepository()
-    private val retailModeRepository = FakeRetailModeRepository()
-
-    @Mock private lateinit var customTileStatePersister: CustomTileStatePersister
-
-    @Mock private lateinit var userTracker: UserTracker
-
-    @Mock private lateinit var logger: QSPipelineLogger
-
-    @Mock private lateinit var newQSTileFactory: NewQSTileFactory
-
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    private val kosmos =
+        testKosmos().apply {
+            qsTileFactory = FakeQSFactory { tileCreator(it) }
+            fakeUserTracker.set(listOf(USER_INFO_0), 0)
+            fakeUserRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
+            tileLifecycleManagerFactory = TLMFactory()
+            newQSTileFactory = mock()
+            qsLogger = mock()
+        }
 
     private val unavailableTiles = mutableSetOf("e")
 
-    private lateinit var underTest: CurrentTilesInteractorImpl
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-
-        mSetFlagsRule.enableFlags(FLAG_QS_NEW_TILES)
-
-        userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
-
-        setUserTracker(0)
-
-        underTest =
-            CurrentTilesInteractorImpl(
-                tileSpecRepository = tileSpecRepository,
-                installedTilesComponentRepository = installedTilesPackageRepository,
-                userRepository = userRepository,
-                minimumTilesRepository = minimumTilesRepository,
-                retailModeRepository = retailModeRepository,
-                customTileStatePersister = customTileStatePersister,
-                tileFactory = tileFactory,
-                newQSTileFactory = { newQSTileFactory },
-                customTileAddedRepository = customTileAddedRepository,
-                tileLifecycleManagerFactory = tileLifecycleManagerFactory,
-                userTracker = userTracker,
-                mainDispatcher = testDispatcher,
-                backgroundDispatcher = testDispatcher,
-                scope = testScope.backgroundScope,
-                logger = logger,
-                featureFlags = pipelineFlags,
-            )
-    }
+    private val underTest = kosmos.currentTilesInteractor
 
     @Test
     fun initialState() =
-        testScope.runTest(USER_INFO_0) {
-            assertThat(underTest.currentTiles.value).isEmpty()
-            assertThat(underTest.currentQSTiles).isEmpty()
-            assertThat(underTest.currentTilesSpecs).isEmpty()
-            assertThat(underTest.userId.value).isEqualTo(0)
-            assertThat(underTest.userContext.value.userId).isEqualTo(0)
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                assertThat(underTest.currentTiles.value).isEmpty()
+                assertThat(underTest.currentQSTiles).isEmpty()
+                assertThat(underTest.currentTilesSpecs).isEmpty()
+                assertThat(underTest.userId.value).isEqualTo(0)
+                assertThat(underTest.userContext.value.userId).isEqualTo(0)
+            }
         }
 
     @Test
     fun correctTiles() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(underTest.currentTiles)
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(underTest.currentTiles)
 
-            val specs =
-                listOf(
-                    TileSpec.create("a"),
-                    TileSpec.create("e"),
-                    CUSTOM_TILE_SPEC,
-                    TileSpec.create("d"),
-                    TileSpec.create("non_existent"),
-                )
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                val specs =
+                    listOf(
+                        TileSpec.create("a"),
+                        TileSpec.create("e"),
+                        CUSTOM_TILE_SPEC,
+                        TileSpec.create("d"),
+                        TileSpec.create("non_existent"),
+                    )
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
 
-            // check each tile
+                // check each tile
 
-            // Tile a
-            val tile0 = tiles!![0]
-            assertThat(tile0.spec).isEqualTo(specs[0])
-            assertThat(tile0.tile.tileSpec).isEqualTo(specs[0].spec)
-            assertThat(tile0.tile).isInstanceOf(FakeQSTile::class.java)
-            assertThat(tile0.tile.isAvailable).isTrue()
+                // Tile a
+                val tile0 = tiles!![0]
+                assertThat(tile0.spec).isEqualTo(specs[0])
+                assertThat(tile0.tile.tileSpec).isEqualTo(specs[0].spec)
+                assertThat(tile0.tile).isInstanceOf(FakeQSTile::class.java)
+                assertThat(tile0.tile.isAvailable).isTrue()
 
-            // Tile e is not available and is not in the list
+                // Tile e is not available and is not in the list
 
-            // Custom Tile
-            val tile1 = tiles!![1]
-            assertThat(tile1.spec).isEqualTo(specs[2])
-            assertThat(tile1.tile.tileSpec).isEqualTo(specs[2].spec)
-            assertThat(tile1.tile).isInstanceOf(CustomTile::class.java)
-            assertThat(tile1.tile.isAvailable).isTrue()
+                // Custom Tile
+                val tile1 = tiles!![1]
+                assertThat(tile1.spec).isEqualTo(specs[2])
+                assertThat(tile1.tile.tileSpec).isEqualTo(specs[2].spec)
+                assertThat(tile1.tile).isInstanceOf(CustomTile::class.java)
+                assertThat(tile1.tile.isAvailable).isTrue()
 
-            // Tile d
-            val tile2 = tiles!![2]
-            assertThat(tile2.spec).isEqualTo(specs[3])
-            assertThat(tile2.tile.tileSpec).isEqualTo(specs[3].spec)
-            assertThat(tile2.tile).isInstanceOf(FakeQSTile::class.java)
-            assertThat(tile2.tile.isAvailable).isTrue()
+                // Tile d
+                val tile2 = tiles!![2]
+                assertThat(tile2.spec).isEqualTo(specs[3])
+                assertThat(tile2.tile.tileSpec).isEqualTo(specs[3].spec)
+                assertThat(tile2.tile).isInstanceOf(FakeQSTile::class.java)
+                assertThat(tile2.tile.isAvailable).isTrue()
 
-            // Tile non-existent shouldn't be created. Therefore, only 3 tiles total
-            assertThat(tiles?.size).isEqualTo(3)
+                // Tile non-existent shouldn't be created. Therefore, only 3 tiles total
+                assertThat(tiles?.size).isEqualTo(3)
+            }
         }
 
     @Test
     fun logTileCreated() =
-        testScope.runTest(USER_INFO_0) {
-            val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
-            runCurrent()
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                runCurrent()
 
-            specs.forEach { verify(logger).logTileCreated(it) }
+                specs.forEach { verify(qsLogger).logTileCreated(it) }
+            }
         }
 
     @Test
     fun logTileNotFoundInFactory() =
-        testScope.runTest(USER_INFO_0) {
-            val specs = listOf(TileSpec.create("non_existing"))
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
-            runCurrent()
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val specs = listOf(TileSpec.create("non_existing"))
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                runCurrent()
 
-            verify(logger, never()).logTileCreated(any())
-            verify(logger).logTileNotFoundInFactory(specs[0])
+                verify(qsLogger, never()).logTileCreated(any())
+                verify(qsLogger).logTileNotFoundInFactory(specs[0])
+            }
         }
 
     @Test
     fun tileNotAvailableDestroyed_logged() =
-        testScope.runTest(USER_INFO_0) {
-            val specs = listOf(TileSpec.create("e"))
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
-            runCurrent()
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val specs = listOf(TileSpec.create("e"))
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                runCurrent()
 
-            verify(logger, never()).logTileCreated(any())
-            verify(logger)
-                .logTileDestroyed(
-                    specs[0],
-                    QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE,
-                )
+                verify(qsLogger, never()).logTileCreated(any())
+                verify(qsLogger)
+                    .logTileDestroyed(
+                        specs[0],
+                        QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE,
+                    )
+            }
         }
 
     @Test
     fun someTilesNotValid_repositorySetToDefinitiveList() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
 
-            val specs = listOf(TileSpec.create("a"), TileSpec.create("e"))
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                val specs = listOf(TileSpec.create("a"), TileSpec.create("e"))
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
 
-            assertThat(tiles).isEqualTo(listOf(TileSpec.create("a")))
+                assertThat(tiles).isEqualTo(listOf(TileSpec.create("a")))
+            }
         }
 
     @Test
     fun deduplicatedTiles() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(underTest.currentTiles)
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(underTest.currentTiles)
 
-            val specs = listOf(TileSpec.create("a"), TileSpec.create("a"))
+                val specs = listOf(TileSpec.create("a"), TileSpec.create("a"))
 
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
 
-            assertThat(tiles?.size).isEqualTo(1)
-            assertThat(tiles!![0].spec).isEqualTo(specs[0])
+                assertThat(tiles?.size).isEqualTo(1)
+                assertThat(tiles!![0].spec).isEqualTo(specs[0])
+            }
         }
 
     @Test
     fun tilesChange_platformTileNotRecreated() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(underTest.currentTiles)
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(underTest.currentTiles)
 
-            val specs = listOf(TileSpec.create("a"))
+                val specs = listOf(TileSpec.create("a"))
 
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
-            val originalTileA = tiles!![0].tile
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                val originalTileA = tiles!![0].tile
 
-            tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b"))
+                tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b"))
 
-            assertThat(tiles?.size).isEqualTo(2)
-            assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA)
+                assertThat(tiles?.size).isEqualTo(2)
+                assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA)
+            }
         }
 
     @Test
     fun tileRemovedIsDestroyed() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(underTest.currentTiles)
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(underTest.currentTiles)
 
-            val specs = listOf(TileSpec.create("a"), TileSpec.create("c"))
+                val specs = listOf(TileSpec.create("a"), TileSpec.create("c"))
 
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
-            val originalTileC = tiles!![1].tile
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                val originalTileC = tiles!![1].tile
 
-            tileSpecRepository.removeTiles(USER_INFO_0.id, listOf(TileSpec.create("c")))
+                tileSpecRepository.removeTiles(USER_INFO_0.id, listOf(TileSpec.create("c")))
 
-            assertThat(tiles?.size).isEqualTo(1)
-            assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("a"))
+                assertThat(tiles?.size).isEqualTo(1)
+                assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("a"))
 
-            assertThat((originalTileC as FakeQSTile).destroyed).isTrue()
-            verify(logger)
-                .logTileDestroyed(
-                    TileSpec.create("c"),
-                    QSPipelineLogger.TileDestroyedReason.TILE_REMOVED,
-                )
+                assertThat(originalTileC.isDestroyed).isTrue()
+                verify(qsLogger)
+                    .logTileDestroyed(
+                        TileSpec.create("c"),
+                        QSPipelineLogger.TileDestroyedReason.TILE_REMOVED,
+                    )
+            }
         }
 
     @Test
     fun tileBecomesNotAvailable_destroyed() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(underTest.currentTiles)
-            val repoTiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(underTest.currentTiles)
+                val repoTiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
 
-            val specs = listOf(TileSpec.create("a"))
+                val specs = listOf(TileSpec.create("a"))
 
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
-            val originalTileA = tiles!![0].tile
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                val originalTileA = tiles!![0].tile
 
-            // Tile becomes unavailable
-            (originalTileA as FakeQSTile).available = false
-            unavailableTiles.add("a")
-            // and there is some change in the specs
-            tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b"))
-            runCurrent()
+                // Tile becomes unavailable
+                (originalTileA as FakeQSTile).available = false
+                unavailableTiles.add("a")
+                // and there is some change in the specs
+                tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b"))
+                runCurrent()
 
-            assertThat(originalTileA.destroyed).isTrue()
-            verify(logger)
-                .logTileDestroyed(
-                    TileSpec.create("a"),
-                    QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE,
-                )
+                assertThat(originalTileA.isDestroyed).isTrue()
+                verify(qsLogger)
+                    .logTileDestroyed(
+                        TileSpec.create("a"),
+                        QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE,
+                    )
 
-            assertThat(tiles?.size).isEqualTo(1)
-            assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("b"))
-            assertThat(tiles!![0].tile).isNotSameInstanceAs(originalTileA)
+                assertThat(tiles?.size).isEqualTo(1)
+                assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("b"))
+                assertThat(tiles!![0].tile).isNotSameInstanceAs(originalTileA)
 
-            assertThat(repoTiles).isEqualTo(tiles!!.map(TileModel::spec))
+                assertThat(repoTiles).isEqualTo(tiles!!.map(TileModel::spec))
+            }
         }
 
     @Test
     fun userChange_tilesChange() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(underTest.currentTiles)
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(underTest.currentTiles)
 
-            val specs0 = listOf(TileSpec.create("a"))
-            val specs1 = listOf(TileSpec.create("b"))
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs0)
-            tileSpecRepository.setTiles(USER_INFO_1.id, specs1)
+                val specs0 = listOf(TileSpec.create("a"))
+                val specs1 = listOf(TileSpec.create("b"))
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs0)
+                tileSpecRepository.setTiles(USER_INFO_1.id, specs1)
 
-            switchUser(USER_INFO_1)
+                switchUser(USER_INFO_1)
 
-            assertThat(tiles!![0].spec).isEqualTo(specs1[0])
-            assertThat(tiles!![0].tile.tileSpec).isEqualTo(specs1[0].spec)
+                assertThat(tiles!![0].spec).isEqualTo(specs1[0])
+                assertThat(tiles!![0].tile.tileSpec).isEqualTo(specs1[0].spec)
+            }
         }
 
     @Test
     fun tileNotPresentInSecondaryUser_destroyedInUserChange() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(underTest.currentTiles)
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(underTest.currentTiles)
 
-            val specs0 = listOf(TileSpec.create("a"))
-            val specs1 = listOf(TileSpec.create("b"))
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs0)
-            tileSpecRepository.setTiles(USER_INFO_1.id, specs1)
+                val specs0 = listOf(TileSpec.create("a"))
+                val specs1 = listOf(TileSpec.create("b"))
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs0)
+                tileSpecRepository.setTiles(USER_INFO_1.id, specs1)
 
-            val originalTileA = tiles!![0].tile
+                val originalTileA = tiles!![0].tile
 
-            switchUser(USER_INFO_1)
-            runCurrent()
+                switchUser(USER_INFO_1)
+                runCurrent()
 
-            assertThat((originalTileA as FakeQSTile).destroyed).isTrue()
-            verify(logger)
-                .logTileDestroyed(
-                    specs0[0],
-                    QSPipelineLogger.TileDestroyedReason.TILE_NOT_PRESENT_IN_NEW_USER,
-                )
+                assertThat(originalTileA.isDestroyed).isTrue()
+                verify(qsLogger)
+                    .logTileDestroyed(
+                        specs0[0],
+                        QSPipelineLogger.TileDestroyedReason.TILE_NOT_PRESENT_IN_NEW_USER,
+                    )
+            }
         }
 
     @Test
-    fun userChange_customTileDestroyed_lifecycleNotTerminated() {
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(underTest.currentTiles)
+    fun userChange_customTileDestroyed_lifecycleNotTerminated() =
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(underTest.currentTiles)
 
-            val specs = listOf(CUSTOM_TILE_SPEC)
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
-            tileSpecRepository.setTiles(USER_INFO_1.id, specs)
+                val specs = listOf(CUSTOM_TILE_SPEC)
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                tileSpecRepository.setTiles(USER_INFO_1.id, specs)
 
-            val originalCustomTile = tiles!![0].tile
+                val originalCustomTile = tiles!![0].tile
 
-            switchUser(USER_INFO_1)
-            runCurrent()
+                switchUser(USER_INFO_1)
+                runCurrent()
 
-            verify(originalCustomTile).destroy()
-            assertThat(tileLifecycleManagerFactory.created).isEmpty()
+                verify(originalCustomTile).destroy()
+                assertThat((tileLifecycleManagerFactory as TLMFactory).created).isEmpty()
+            }
         }
-    }
 
     @Test
     fun userChange_sameTileUserChanged() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(underTest.currentTiles)
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(underTest.currentTiles)
 
-            val specs = listOf(TileSpec.create("a"))
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
-            tileSpecRepository.setTiles(USER_INFO_1.id, specs)
+                val specs = listOf(TileSpec.create("a"))
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                tileSpecRepository.setTiles(USER_INFO_1.id, specs)
 
-            val originalTileA = tiles!![0].tile as FakeQSTile
-            assertThat(originalTileA.user).isEqualTo(USER_INFO_0.id)
+                val originalTileA = tiles!![0].tile as FakeQSTile
+                assertThat(originalTileA.user).isEqualTo(USER_INFO_0.id)
 
-            switchUser(USER_INFO_1)
-            runCurrent()
+                switchUser(USER_INFO_1)
+                runCurrent()
 
-            assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA)
-            assertThat(originalTileA.user).isEqualTo(USER_INFO_1.id)
-            verify(logger).logTileUserChanged(specs[0], USER_INFO_1.id)
+                assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA)
+                assertThat(originalTileA.user).isEqualTo(USER_INFO_1.id)
+                verify(qsLogger).logTileUserChanged(specs[0], USER_INFO_1.id)
+            }
         }
 
     @Test
     fun addTile() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
-            val spec = TileSpec.create("a")
-            val currentSpecs = listOf(TileSpec.create("b"), TileSpec.create("c"))
-            tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+                val spec = TileSpec.create("a")
+                val currentSpecs = listOf(TileSpec.create("b"), TileSpec.create("c"))
+                tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
 
-            underTest.addTile(spec, position = 1)
+                underTest.addTile(spec, position = 1)
 
-            val expectedSpecs = listOf(TileSpec.create("b"), spec, TileSpec.create("c"))
-            assertThat(tiles).isEqualTo(expectedSpecs)
+                val expectedSpecs = listOf(TileSpec.create("b"), spec, TileSpec.create("c"))
+                assertThat(tiles).isEqualTo(expectedSpecs)
+            }
         }
 
     @Test
     fun addTile_currentUser() =
-        testScope.runTest(USER_INFO_1) {
-            val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
-            val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
-            val spec = TileSpec.create("a")
-            val currentSpecs = listOf(TileSpec.create("b"), TileSpec.create("c"))
-            tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
-            tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)
+        with(kosmos) {
+            testScope.runTest(USER_INFO_1) {
+                val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+                val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
+                val spec = TileSpec.create("a")
+                val currentSpecs = listOf(TileSpec.create("b"), TileSpec.create("c"))
+                tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+                tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)
 
-            switchUser(USER_INFO_1)
-            underTest.addTile(spec, position = 1)
+                switchUser(USER_INFO_1)
+                underTest.addTile(spec, position = 1)
 
-            assertThat(tiles0).isEqualTo(currentSpecs)
+                assertThat(tiles0).isEqualTo(currentSpecs)
 
-            val expectedSpecs = listOf(TileSpec.create("b"), spec, TileSpec.create("c"))
-            assertThat(tiles1).isEqualTo(expectedSpecs)
+                val expectedSpecs = listOf(TileSpec.create("b"), spec, TileSpec.create("c"))
+                assertThat(tiles1).isEqualTo(expectedSpecs)
+            }
         }
 
     @Test
     fun removeTile_platform() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
 
-            val specs = listOf(TileSpec.create("a"), TileSpec.create("b"))
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
-            runCurrent()
+                val specs = listOf(TileSpec.create("a"), TileSpec.create("b"))
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                runCurrent()
 
-            underTest.removeTiles(specs.subList(0, 1))
+                underTest.removeTiles(specs.subList(0, 1))
 
-            assertThat(tiles).isEqualTo(specs.subList(1, 2))
+                assertThat(tiles).isEqualTo(specs.subList(1, 2))
+            }
         }
 
     @Test
-    fun removeTile_customTile_lifecycleEnded() {
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+    fun removeTile_customTile_lifecycleEnded() =
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
 
-            val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
-            runCurrent()
-            assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
-                .isTrue()
+                val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                runCurrent()
+                assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
+                    .isTrue()
 
-            underTest.removeTiles(listOf(CUSTOM_TILE_SPEC))
+                underTest.removeTiles(listOf(CUSTOM_TILE_SPEC))
 
-            assertThat(tiles).isEqualTo(specs.subList(0, 1))
+                assertThat(tiles).isEqualTo(specs.subList(0, 1))
 
-            val tileLifecycleManager =
-                tileLifecycleManagerFactory.created[USER_INFO_0.id to TEST_COMPONENT]
-            assertThat(tileLifecycleManager).isNotNull()
+                val tileLifecycleManager =
+                    (tileLifecycleManagerFactory as TLMFactory)
+                        .created[USER_INFO_0.id to TEST_COMPONENT]
+                assertThat(tileLifecycleManager).isNotNull()
 
-            with(inOrder(tileLifecycleManager!!)) {
-                verify(tileLifecycleManager).onStopListening()
-                verify(tileLifecycleManager).onTileRemoved()
-                verify(tileLifecycleManager).flushMessagesAndUnbind()
+                with(inOrder(tileLifecycleManager!!)) {
+                    verify(tileLifecycleManager).onStopListening()
+                    verify(tileLifecycleManager).onTileRemoved()
+                    verify(tileLifecycleManager).flushMessagesAndUnbind()
+                }
+                assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
+                    .isFalse()
+                assertThat(
+                        customTileStatePersister.readState(
+                            TileServiceKey(TEST_COMPONENT, USER_INFO_0.id)
+                        )
+                    )
+                    .isNull()
             }
-            assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
-                .isFalse()
-            verify(customTileStatePersister)
-                .removeState(TileServiceKey(TEST_COMPONENT, USER_INFO_0.id))
         }
-    }
 
     @Test
     fun removeTiles_currentUser() =
-        testScope.runTest {
-            val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
-            val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
-            val currentSpecs =
-                listOf(TileSpec.create("a"), TileSpec.create("b"), TileSpec.create("c"))
-            tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
-            tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)
+        with(kosmos) {
+            testScope.runTest {
+                val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+                val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
+                val currentSpecs =
+                    listOf(TileSpec.create("a"), TileSpec.create("b"), TileSpec.create("c"))
+                tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+                tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)
 
-            switchUser(USER_INFO_1)
-            runCurrent()
+                switchUser(USER_INFO_1)
+                runCurrent()
 
-            underTest.removeTiles(currentSpecs.subList(0, 2))
+                underTest.removeTiles(currentSpecs.subList(0, 2))
 
-            assertThat(tiles0).isEqualTo(currentSpecs)
-            assertThat(tiles1).isEqualTo(currentSpecs.subList(2, 3))
+                assertThat(tiles0).isEqualTo(currentSpecs)
+                assertThat(tiles1).isEqualTo(currentSpecs.subList(2, 3))
+            }
         }
 
     @Test
     fun setTiles() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
 
-            val currentSpecs = listOf(TileSpec.create("a"), TileSpec.create("b"))
-            tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
-            runCurrent()
+                val currentSpecs = listOf(TileSpec.create("a"), TileSpec.create("b"))
+                tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+                runCurrent()
 
-            val newSpecs = listOf(TileSpec.create("b"), TileSpec.create("c"), TileSpec.create("a"))
-            underTest.setTiles(newSpecs)
-            runCurrent()
+                val newSpecs =
+                    listOf(TileSpec.create("b"), TileSpec.create("c"), TileSpec.create("a"))
+                underTest.setTiles(newSpecs)
+                runCurrent()
 
-            assertThat(tiles).isEqualTo(newSpecs)
+                assertThat(tiles).isEqualTo(newSpecs)
+            }
         }
 
     @Test
     fun setTiles_customTiles_lifecycleEndedIfGone() =
-        testScope.runTest(USER_INFO_0) {
-            val otherCustomTileSpec = TileSpec.create("custom(b/c)")
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val otherCustomTileSpec = TileSpec.create("custom(b/c)")
 
-            val currentSpecs = listOf(CUSTOM_TILE_SPEC, TileSpec.create("a"), otherCustomTileSpec)
-            tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
-            runCurrent()
+                val currentSpecs =
+                    listOf(CUSTOM_TILE_SPEC, TileSpec.create("a"), otherCustomTileSpec)
+                tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+                runCurrent()
 
-            val newSpecs = listOf(otherCustomTileSpec, TileSpec.create("a"))
+                val newSpecs = listOf(otherCustomTileSpec, TileSpec.create("a"))
 
-            underTest.setTiles(newSpecs)
-            runCurrent()
+                underTest.setTiles(newSpecs)
+                runCurrent()
 
-            val tileLifecycleManager =
-                tileLifecycleManagerFactory.created[USER_INFO_0.id to TEST_COMPONENT]!!
+                val tileLifecycleManager =
+                    (tileLifecycleManagerFactory as TLMFactory)
+                        .created[USER_INFO_0.id to TEST_COMPONENT]!!
 
-            with(inOrder(tileLifecycleManager)) {
-                verify(tileLifecycleManager).onStopListening()
-                verify(tileLifecycleManager).onTileRemoved()
-                verify(tileLifecycleManager).flushMessagesAndUnbind()
+                with(inOrder(tileLifecycleManager)) {
+                    verify(tileLifecycleManager).onStopListening()
+                    verify(tileLifecycleManager).onTileRemoved()
+                    verify(tileLifecycleManager).flushMessagesAndUnbind()
+                }
+                assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
+                    .isFalse()
+                assertThat(
+                        customTileStatePersister.readState(
+                            TileServiceKey(TEST_COMPONENT, USER_INFO_0.id)
+                        )
+                    )
+                    .isNull()
             }
-            assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
-                .isFalse()
-            verify(customTileStatePersister)
-                .removeState(TileServiceKey(TEST_COMPONENT, USER_INFO_0.id))
         }
 
     @Test
     fun protoDump() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(underTest.currentTiles)
-            val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(underTest.currentTiles)
+                val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
 
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
 
-            val stateA = tiles!![0].tile.state
-            stateA.fillIn(Tile.STATE_INACTIVE, "A", "AA")
-            val stateCustom = QSTile.BooleanState()
-            stateCustom.fillIn(Tile.STATE_ACTIVE, "B", "BB")
-            stateCustom.spec = CUSTOM_TILE_SPEC.spec
-            whenever(tiles!![1].tile.state).thenReturn(stateCustom)
+                val stateA = tiles!![0].tile.state
+                stateA.fillIn(Tile.STATE_INACTIVE, "A", "AA")
+                val stateCustom = QSTile.BooleanState()
+                stateCustom.fillIn(Tile.STATE_ACTIVE, "B", "BB")
+                stateCustom.spec = CUSTOM_TILE_SPEC.spec
+                whenever(tiles!![1].tile.state).thenReturn(stateCustom)
 
-            val proto = SystemUIProtoDump()
-            underTest.dumpProto(proto, emptyArray())
+                val proto = SystemUIProtoDump()
+                underTest.dumpProto(proto, emptyArray())
 
-            assertThat(MessageNano.messageNanoEquals(proto.tiles[0], stateA.toProto())).isTrue()
-            assertThat(MessageNano.messageNanoEquals(proto.tiles[1], stateCustom.toProto()))
-                .isTrue()
+                assertThat(MessageNano.messageNanoEquals(proto.tiles[0], stateA.toProto())).isTrue()
+                assertThat(MessageNano.messageNanoEquals(proto.tiles[1], stateCustom.toProto()))
+                    .isTrue()
+            }
         }
 
     @Test
     fun retainedTiles_callbackNotRemoved() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(underTest.currentTiles)
-            tileSpecRepository.setTiles(USER_INFO_0.id, listOf(TileSpec.create("a")))
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(underTest.currentTiles)
+                tileSpecRepository.setTiles(USER_INFO_0.id, listOf(TileSpec.create("a")))
 
-            val tileA = tiles!![0].tile
-            val callback = mock<QSTile.Callback>()
-            tileA.addCallback(callback)
+                val tileA = tiles!![0].tile
+                val callback = mock<QSTile.Callback>()
+                tileA.addCallback(callback)
 
-            tileSpecRepository.setTiles(
-                USER_INFO_0.id,
-                listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC),
-            )
-            val newTileA = tiles!![0].tile
-            assertThat(tileA).isSameInstanceAs(newTileA)
+                tileSpecRepository.setTiles(
+                    USER_INFO_0.id,
+                    listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC),
+                )
+                val newTileA = tiles!![0].tile
+                assertThat(tileA).isSameInstanceAs(newTileA)
 
-            assertThat((tileA as FakeQSTile).callbacks).containsExactly(callback)
+                assertThat((tileA as FakeQSTile).callbacks).containsExactly(callback)
+            }
         }
 
     @Test
     fun packageNotInstalled_customTileNotVisible() =
-        testScope.runTest(USER_INFO_0) {
-            installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                fakeInstalledTilesRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
 
-            val tiles by collectLastValue(underTest.currentTiles)
+                val tiles by collectLastValue(underTest.currentTiles)
 
-            val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
 
-            assertThat(tiles!!.size).isEqualTo(1)
-            assertThat(tiles!![0].spec).isEqualTo(specs[0])
+                assertThat(tiles!!.size).isEqualTo(1)
+                assertThat(tiles!![0].spec).isEqualTo(specs[0])
+            }
         }
 
     @Test
     fun packageInstalledLater_customTileAdded() =
-        testScope.runTest(USER_INFO_0) {
-            installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                fakeInstalledTilesRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
 
-            val tiles by collectLastValue(underTest.currentTiles)
-            val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC, TileSpec.create("b"))
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                val tiles by collectLastValue(underTest.currentTiles)
+                val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC, TileSpec.create("b"))
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
 
-            assertThat(tiles!!.size).isEqualTo(2)
+                assertThat(tiles!!.size).isEqualTo(2)
 
-            installedTilesPackageRepository.setInstalledPackagesForUser(
-                USER_INFO_0.id,
-                setOf(TEST_COMPONENT),
-            )
+                fakeInstalledTilesRepository.setInstalledPackagesForUser(
+                    USER_INFO_0.id,
+                    setOf(TEST_COMPONENT),
+                )
 
-            assertThat(tiles!!.size).isEqualTo(3)
-            assertThat(tiles!![1].spec).isEqualTo(CUSTOM_TILE_SPEC)
+                assertThat(tiles!!.size).isEqualTo(3)
+                assertThat(tiles!![1].spec).isEqualTo(CUSTOM_TILE_SPEC)
+            }
         }
 
     @Test
     fun tileAddedOnEmptyList_blocked() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(underTest.currentTiles)
-            val specs = listOf(TileSpec.create("a"), TileSpec.create("b"))
-            val newTile = TileSpec.create("c")
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(underTest.currentTiles)
+                val specs = listOf(TileSpec.create("a"), TileSpec.create("b"))
+                val newTile = TileSpec.create("c")
 
-            underTest.addTile(newTile)
+                underTest.addTile(newTile)
 
-            assertThat(tiles!!.isEmpty()).isTrue()
+                assertThat(tiles!!.isEmpty()).isTrue()
 
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
 
-            assertThat(tiles!!.size).isEqualTo(3)
+                assertThat(tiles!!.size).isEqualTo(3)
+            }
         }
 
     @Test
     fun changeInPackagesTiles_doesntTriggerUserChange_logged() =
-        testScope.runTest(USER_INFO_0) {
-            val specs = listOf(TileSpec.create("a"))
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
-            runCurrent()
-            // Settled on the same list of tiles.
-            assertThat(underTest.currentTilesSpecs).isEqualTo(specs)
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val specs = listOf(TileSpec.create("a"))
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                runCurrent()
+                // Settled on the same list of tiles.
+                assertThat(underTest.currentTilesSpecs).isEqualTo(specs)
 
-            installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
-            runCurrent()
+                fakeInstalledTilesRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
+                runCurrent()
 
-            verify(logger, never()).logTileUserChanged(TileSpec.create("a"), 0)
+                verify(qsLogger, never()).logTileUserChanged(TileSpec.create("a"), 0)
+            }
         }
 
     @Test
     fun getTileDetails() =
-        testScope.runTest(USER_INFO_0) {
-            val tiles by collectLastValue(underTest.currentTiles)
-            val tileA = TileSpec.create("a")
-            val tileB = TileSpec.create("b")
-            val tileNoDetails = TileSpec.create("NoDetails")
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(underTest.currentTiles)
+                val tileA = TileSpec.create("a")
+                val tileB = TileSpec.create("b")
+                val tileNoDetails = TileSpec.create("NoDetails")
 
-            val specs = listOf(tileA, tileB, tileNoDetails)
+                val specs = listOf(tileA, tileB, tileNoDetails)
 
-            assertThat(tiles!!.isEmpty()).isTrue()
+                assertThat(tiles!!.isEmpty()).isTrue()
 
-            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
-            assertThat(tiles!!.size).isEqualTo(3)
+                tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+                assertThat(tiles!!.size).isEqualTo(3)
 
-            // The third tile doesn't have a details view.
-            assertThat(tiles!![2].spec).isEqualTo(tileNoDetails)
-            (tiles!![2].tile as FakeQSTile).hasDetailsViewModel = false
+                // The third tile doesn't have a details view.
+                assertThat(tiles!![2].spec).isEqualTo(tileNoDetails)
+                (tiles!![2].tile as FakeQSTile).hasDetailsViewModel = false
 
-            var currentModel: TileDetailsViewModel? = null
-            val setCurrentModel = { model: TileDetailsViewModel? -> currentModel = model }
-            tiles!![0].tile.getDetailsViewModel(setCurrentModel)
-            assertThat(currentModel?.getTitle()).isEqualTo("a")
+                var currentModel: TileDetailsViewModel? = null
+                val setCurrentModel = { model: TileDetailsViewModel? -> currentModel = model }
+                tiles!![0].tile.getDetailsViewModel(setCurrentModel)
+                assertThat(currentModel?.getTitle()).isEqualTo("a")
 
-            currentModel = null
-            tiles!![1].tile.getDetailsViewModel(setCurrentModel)
-            assertThat(currentModel?.getTitle()).isEqualTo("b")
+                currentModel = null
+                tiles!![1].tile.getDetailsViewModel(setCurrentModel)
+                assertThat(currentModel?.getTitle()).isEqualTo("b")
 
-            currentModel = null
-            tiles!![2].tile.getDetailsViewModel(setCurrentModel)
-            assertThat(currentModel).isNull()
+                currentModel = null
+                tiles!![2].tile.getDetailsViewModel(setCurrentModel)
+                assertThat(currentModel).isNull()
+            }
+        }
+
+    @Test
+    fun destroyedTilesNotReused() =
+        with(kosmos) {
+            testScope.runTest(USER_INFO_0) {
+                val tiles by collectLastValue(underTest.currentTiles)
+                val specs = listOf(TileSpec.create("a"), TileSpec.create("b"))
+                val newTile = TileSpec.create("c")
+
+                underTest.setTiles(specs)
+
+                val tileABefore = tiles!!.first { it.spec == specs[0] }.tile
+
+                // We destroy it manually, in prod, this could happen if the tile processing action
+                // is interrupted in the middle.
+                tileABefore.destroy()
+
+                underTest.addTile(newTile)
+
+                val tileAAfter = tiles!!.first { it.spec == specs[0] }.tile
+                assertThat(tileAAfter).isNotSameInstanceAs(tileABefore)
+            }
         }
 
     private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
@@ -686,20 +735,21 @@
         }
     }
 
-    private fun tileCreator(spec: String): QSTile? {
-        val currentUser = userTracker.userId
+    private fun Kosmos.tileCreator(spec: String): QSTile? {
+        val currentUser = userRepository.getSelectedUserInfo().id
         return when (spec) {
             CUSTOM_TILE_SPEC.spec ->
                 mock<CustomTile> {
                     var tileSpecReference: String? = null
-                    whenever(user).thenReturn(currentUser)
-                    whenever(component).thenReturn(CUSTOM_TILE_SPEC.componentName)
-                    whenever(isAvailable).thenReturn(true)
-                    whenever(setTileSpec(anyString())).thenAnswer {
-                        tileSpecReference = it.arguments[0] as? String
-                        Unit
-                    }
-                    whenever(tileSpec).thenAnswer { tileSpecReference }
+                    on { user } doReturn currentUser
+                    on { component } doReturn CUSTOM_TILE_SPEC.componentName
+                    on { isAvailable } doReturn true
+                    on { setTileSpec(anyString()) }
+                        .thenAnswer {
+                            tileSpecReference = it.arguments[0] as? String
+                            Unit
+                        }
+                    on { tileSpec }.thenAnswer { tileSpecReference }
                     // Also, add it to the set of added tiles (as this happens as part of the tile
                     // creation).
                     customTileAddedRepository.setTileAdded(
@@ -714,22 +764,16 @@
     }
 
     private fun TestScope.runTest(user: UserInfo, body: suspend TestScope.() -> Unit) {
-        return runTest {
+        return kosmos.runTest {
             switchUser(user)
             body()
         }
     }
 
-    private suspend fun switchUser(user: UserInfo) {
-        setUserTracker(user.id)
-        installedTilesPackageRepository.setInstalledPackagesForUser(user.id, setOf(TEST_COMPONENT))
-        userRepository.setSelectedUserInfo(user)
-    }
-
-    private fun setUserTracker(user: Int) {
-        val mockContext = mockUserContext(user)
-        whenever(userTracker.userContext).thenReturn(mockContext)
-        whenever(userTracker.userId).thenReturn(user)
+    private suspend fun Kosmos.switchUser(user: UserInfo) {
+        fakeUserTracker.set(listOf(user), 0)
+        fakeInstalledTilesRepository.setInstalledPackagesForUser(user.id, setOf(TEST_COMPONENT))
+        fakeUserRepository.setSelectedUserInfo(user)
     }
 
     private class TLMFactory : TileLifecycleManager.Factory {
@@ -745,13 +789,6 @@
         }
     }
 
-    private fun mockUserContext(user: Int): Context {
-        return mock {
-            whenever(this.userId).thenReturn(user)
-            whenever(this.user).thenReturn(UserHandle.of(user))
-        }
-    }
-
     companion object {
         private val USER_INFO_0 = UserInfo().apply { id = 0 }
         private val USER_INFO_1 = UserInfo().apply { id = 1 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 296478b..1d8c6cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -508,6 +508,12 @@
         assertThat(mTile.mRefreshes).isEqualTo(1);
     }
 
+    @Test
+    public void testIsDestroyedImmediately() {
+        mTile.destroy();
+        assertThat(mTile.isDestroyed()).isTrue();
+    }
+
     private void assertEvent(UiEventLogger.UiEventEnum eventType,
             UiEventLoggerFake.FakeUiEvent fakeEvent) {
         assertEquals(eventType.getId(), fakeEvent.eventId);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
index fba6151..da3cebd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.qs.FakeTileDetailsViewModel
 import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
 import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
 import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
@@ -97,6 +98,7 @@
                 testCoroutineDispatcher,
                 testCoroutineDispatcher,
                 testScope.backgroundScope,
+                FakeTileDetailsViewModel("QSTileViewModelImplTest"),
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
index 3db5efc..261e3de 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
@@ -26,8 +26,6 @@
 import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
-import com.android.systemui.qs.tiles.dialog.InternetDetailsContentManager
-import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel
 import com.android.systemui.qs.tiles.dialog.InternetDialogManager
 import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
 import com.android.systemui.statusbar.connectivity.AccessPointController
@@ -39,11 +37,8 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.eq
-import org.mockito.kotlin.any
 import org.mockito.kotlin.mock
-import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
 
 @SmallTest
 @EnabledOnRavenwood
@@ -56,31 +51,17 @@
 
     private lateinit var internetDialogManager: InternetDialogManager
     private lateinit var controller: AccessPointController
-    private lateinit var internetDetailsViewModelFactory: InternetDetailsViewModel.Factory
-    private lateinit var internetDetailsContentManagerFactory: InternetDetailsContentManager.Factory
-    private lateinit var internetDetailsViewModel: InternetDetailsViewModel
 
     @Before
     fun setup() {
         internetDialogManager = mock<InternetDialogManager>()
         controller = mock<AccessPointController>()
-        internetDetailsViewModelFactory = mock<InternetDetailsViewModel.Factory>()
-        internetDetailsContentManagerFactory = mock<InternetDetailsContentManager.Factory>()
-        internetDetailsViewModel =
-            InternetDetailsViewModel(
-                onLongClick = {},
-                accessPointController = mock<AccessPointController>(),
-                contentManagerFactory = internetDetailsContentManagerFactory,
-            )
-        whenever(internetDetailsViewModelFactory.create(any())).thenReturn(internetDetailsViewModel)
-
         underTest =
             InternetTileUserActionInteractor(
                 kosmos.testScope.coroutineContext,
                 internetDialogManager,
                 controller,
                 inputHandler,
-                internetDetailsViewModelFactory,
             )
     }
 
@@ -127,12 +108,4 @@
                 assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS)
             }
         }
-
-    @Test
-    fun detailsViewModel() =
-        kosmos.testScope.runTest {
-            assertThat(underTest.detailsViewModel.getTitle()).isEqualTo("Internet")
-            assertThat(underTest.detailsViewModel.getSubTitle())
-                .isEqualTo("Tab a network to connect")
-        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
index 0598a8b..4e9b635 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.FakeTileDetailsViewModel
 import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
 import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
@@ -171,21 +172,6 @@
                 .isEqualTo(FakeQSTileDataInteractor.AvailabilityRequest(USER))
         }
 
-    @Test
-    fun tileDetails() =
-        testScope.runTest {
-            assertThat(tileUserActionInteractor.detailsViewModel).isNotNull()
-            assertThat(tileUserActionInteractor.detailsViewModel?.getTitle())
-                .isEqualTo("FakeQSTileUserActionInteractor")
-            assertThat(underTest.detailsViewModel).isNotNull()
-            assertThat(underTest.detailsViewModel?.getTitle())
-                .isEqualTo("FakeQSTileUserActionInteractor")
-
-            tileUserActionInteractor.detailsViewModel = null
-            assertThat(tileUserActionInteractor.detailsViewModel).isNull()
-            assertThat(underTest.detailsViewModel).isNull()
-        }
-
     private fun createViewModel(
         scope: TestScope,
         config: QSTileConfig = tileConfig,
@@ -209,6 +195,7 @@
             testCoroutineDispatcher,
             testCoroutineDispatcher,
             scope.backgroundScope,
+            FakeTileDetailsViewModel("QSTileViewModelTest"),
         )
 
     private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
index ece21e1..166e950 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.qs.FakeTileDetailsViewModel
 import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
 import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
@@ -253,5 +254,6 @@
             testCoroutineDispatcher,
             testCoroutineDispatcher,
             scope.backgroundScope,
+            FakeTileDetailsViewModel("QSTileViewModelUserInputTest"),
         )
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
index 510167d..d3cd240 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
@@ -84,7 +84,7 @@
         controller.onIntentStarted(willAnimate = false)
 
         assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification))
-        assertFalse(notification.entry.isExpandAnimationRunning)
+        assertFalse(notification.isLaunchAnimationRunning)
         val isExpandAnimationRunning by
             testScope.collectLastValue(
                 notificationLaunchAnimationInteractor.isLaunchAnimationRunning
@@ -107,7 +107,7 @@
         controller.onTransitionAnimationCancelled()
 
         assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification))
-        assertFalse(notification.entry.isExpandAnimationRunning)
+        assertFalse(notification.isLaunchAnimationRunning)
         val isExpandAnimationRunning by
             testScope.collectLastValue(
                 notificationLaunchAnimationInteractor.isLaunchAnimationRunning
@@ -130,7 +130,7 @@
         controller.onTransitionAnimationEnd(isExpandingFullyAbove = true)
 
         assertFalse(HeadsUpUtil.isClickedHeadsUpNotification(notification))
-        assertFalse(notification.entry.isExpandAnimationRunning)
+        assertFalse(notification.isLaunchAnimationRunning)
         val isExpandAnimationRunning by
             testScope.collectLastValue(
                 notificationLaunchAnimationInteractor.isLaunchAnimationRunning
@@ -199,7 +199,7 @@
     fun testNotificationIsExpandingDuringAnimation() {
         controller.onIntentStarted(willAnimate = true)
 
-        assertTrue(notification.entry.isExpandAnimationRunning)
+        assertTrue(notification.isLaunchAnimationRunning)
         val isExpandAnimationRunning by
             testScope.collectLastValue(
                 notificationLaunchAnimationInteractor.isLaunchAnimationRunning
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 92b8c3a..13da04e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
 import com.android.systemui.statusbar.notification.headsup.AvalancheController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.util.mockito.mock
@@ -47,6 +48,7 @@
     private val statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
     private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
     private val avalancheController = mock<AvalancheController>()
+    private val headsupRepository = mock<HeadsUpRepository>()
 
     private lateinit var sut: AmbientState
 
@@ -72,6 +74,7 @@
                 bypassController,
                 statusBarKeyguardViewManager,
                 largeScreenShadeInterpolator,
+                headsupRepository,
                 avalancheController,
             )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 33211d4..ce655ef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.statusbar.notification.RoundableState
 import com.android.systemui.statusbar.notification.collection.EntryAdapter
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
 import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView.FooterViewState
@@ -61,6 +62,7 @@
     private val dumpManager = mock<DumpManager>()
     private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
     private val notificationShelf = mock<NotificationShelf>()
+    private val headsUpRepository = mock<HeadsUpRepository>()
     private val emptyShadeView =
         EmptyShadeView(context, /* attrs= */ null).apply {
             layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
@@ -74,6 +76,7 @@
             /* bypassController */ { false },
             mStatusBarKeyguardViewManager,
             largeScreenShadeInterpolator,
+            headsUpRepository,
             avalancheController,
         )
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 56176cf..d197cdb7 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -42,7 +42,7 @@
 @DependsOn(target = Icon.class)
 @DependsOn(target = State.class)
 public interface QSTile {
-    int VERSION = 4;
+    int VERSION = 5;
 
     String getTileSpec();
 
@@ -78,6 +78,7 @@
     void longClick(@Nullable Expandable expandable);
 
     void userSwitch(int currentUser);
+    int getCurrentTileUser();
 
     /**
      * @deprecated not needed as {@link com.android.internal.logging.UiEvent} will use
@@ -150,6 +151,8 @@
         return null;
     }
 
+    boolean isDestroyed();
+
     @ProvidesInterface(version = Callback.VERSION)
     interface Callback {
         static final int VERSION = 2;
diff --git a/packages/SystemUI/res/drawable/notification_2025_guts_priority_button_bg.xml b/packages/SystemUI/res/drawable/notification_2025_guts_priority_button_bg.xml
new file mode 100644
index 0000000..1de8c2b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/notification_2025_guts_priority_button_bg.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2025 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
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle" >
+    <solid
+        android:color="@color/notification_guts_priority_button_bg_fill" />
+
+    <stroke
+        android:width="1.5dp"
+        android:color="@color/notification_guts_priority_button_bg_stroke" />
+
+    <corners android:radius="16dp" />
+</shape>
diff --git a/packages/SystemUI/res/layout/notification_2025_info.xml b/packages/SystemUI/res/layout/notification_2025_info.xml
new file mode 100644
index 0000000..7b69166
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_2025_info.xml
@@ -0,0 +1,365 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2025, 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.
+-->
+
+<!-- extends LinearLayout -->
+<com.android.systemui.statusbar.notification.row.NotificationInfo
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:id="@+id/notification_guts"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:focusable="true"
+    android:clipChildren="false"
+    android:clipToPadding="true"
+    android:orientation="vertical"
+    android:paddingStart="@*android:dimen/notification_2025_margin">
+
+    <!-- Package Info -->
+    <LinearLayout
+        android:id="@+id/header"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:clipChildren="false"
+        android:clipToPadding="true">
+        <ImageView
+            android:id="@+id/pkg_icon"
+            android:layout_width="@*android:dimen/notification_2025_icon_circle_size"
+            android:layout_height="@*android:dimen/notification_2025_icon_circle_size"
+            android:layout_marginTop="@*android:dimen/notification_2025_margin"
+            android:layout_marginEnd="@*android:dimen/notification_2025_margin" />
+        <LinearLayout
+            android:id="@+id/names"
+            android:layout_weight="1"
+            android:layout_width="0dp"
+            android:orientation="vertical"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@*android:dimen/notification_2025_margin"
+            android:minHeight="@*android:dimen/notification_2025_icon_circle_size">
+            <TextView
+                android:id="@+id/channel_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textDirection="locale"
+                style="@style/TextAppearance.NotificationImportanceChannel"/>
+            <TextView
+                android:id="@+id/group_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textDirection="locale"
+                android:ellipsize="end"
+                style="@style/TextAppearance.NotificationImportanceChannelGroup"/>
+            <TextView
+                android:id="@+id/pkg_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/TextAppearance.NotificationImportanceApp"
+                android:ellipsize="end"
+                android:textDirection="locale"
+                android:maxLines="1"/>
+            <TextView
+                android:id="@+id/delegate_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/TextAppearance.NotificationImportanceHeader"
+                android:ellipsize="end"
+                android:textDirection="locale"
+                android:text="@string/notification_delegate_header"
+                android:maxLines="1" />
+
+        </LinearLayout>
+
+        <!-- feedback for notificationassistantservice -->
+        <ImageButton
+            android:id="@+id/feedback"
+            android:layout_width="@dimen/notification_2025_guts_button_size"
+            android:layout_height="@dimen/notification_2025_guts_button_size"
+            android:visibility="gone"
+            android:background="@drawable/ripple_drawable"
+            android:contentDescription="@string/notification_guts_bundle_feedback"
+            android:src="@*android:drawable/ic_feedback"
+            android:paddingTop="@*android:dimen/notification_2025_margin"
+            android:tint="@androidprv:color/materialColorPrimary"/>
+
+        <!-- Optional link to app. Only appears if the channel is not disabled and the app
+        asked for it -->
+        <ImageButton
+            android:id="@+id/app_settings"
+            android:layout_width="@dimen/notification_2025_guts_button_size"
+            android:layout_height="@dimen/notification_2025_guts_button_size"
+            android:visibility="gone"
+            android:background="@drawable/ripple_drawable"
+            android:contentDescription="@string/notification_app_settings"
+            android:src="@drawable/ic_info"
+            android:paddingTop="@*android:dimen/notification_2025_margin"
+            android:tint="@androidprv:color/materialColorPrimary"/>
+
+        <!-- System notification settings -->
+        <ImageButton
+            android:id="@+id/info"
+            android:layout_width="@dimen/notification_2025_guts_button_size"
+            android:layout_height="@dimen/notification_2025_guts_button_size"
+            android:contentDescription="@string/notification_more_settings"
+            android:background="@drawable/ripple_drawable"
+            android:src="@drawable/ic_settings"
+            android:padding="@*android:dimen/notification_2025_margin"
+            android:tint="@androidprv:color/materialColorPrimary" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/inline_controls"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@*android:dimen/notification_2025_margin"
+        android:layout_marginTop="@*android:dimen/notification_2025_margin"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:orientation="vertical">
+
+        <!-- Non configurable app/channel text. appears instead of @+id/interruptiveness_settings-->
+        <TextView
+            android:id="@+id/non_configurable_text"
+            android:text="@string/notification_unblockable_desc"
+            android:visibility="gone"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="@*android:style/TextAppearance.DeviceDefault.Notification" />
+
+        <!-- Non configurable app/channel text. appears instead of @+id/interruptiveness_settings-->
+        <TextView
+            android:id="@+id/non_configurable_call_text"
+            android:text="@string/notification_unblockable_call_desc"
+            android:visibility="gone"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="@*android:style/TextAppearance.DeviceDefault.Notification" />
+
+        <!-- Non configurable multichannel text. appears instead of @+id/interruptiveness_settings-->
+        <TextView
+            android:id="@+id/non_configurable_multichannel_text"
+            android:text="@string/notification_multichannel_desc"
+            android:visibility="gone"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="@*android:style/TextAppearance.DeviceDefault.Notification" />
+
+        <LinearLayout
+            android:id="@+id/interruptiveness_settings"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:orientation="vertical">
+            <com.android.systemui.statusbar.notification.row.ButtonLinearLayout
+                android:id="@+id/automatic"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingVertical="@dimen/notification_2025_importance_button_padding_vertical"
+                android:paddingHorizontal="@dimen/notification_2025_importance_button_padding_horizontal"
+                android:gravity="center_vertical"
+                android:clickable="true"
+                android:focusable="true"
+                android:background="@drawable/notification_2025_guts_priority_button_bg"
+                android:orientation="horizontal"
+                android:visibility="gone">
+                <ImageView
+                    android:id="@+id/automatic_icon"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:paddingEnd="@*android:dimen/notification_2025_margin"
+                    android:src="@drawable/ic_notifications_automatic"
+                    android:background="@android:color/transparent"
+                    android:tint="@color/notification_guts_priority_contents"
+                    android:clickable="false"
+                    android:focusable="false"/>
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="vertical"
+                    android:gravity="center"
+                >
+                    <TextView
+                        android:id="@+id/automatic_label"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="1"
+                        android:ellipsize="end"
+                        android:maxLines="1"
+                        android:clickable="false"
+                        android:focusable="false"
+                        android:textAppearance="@style/TextAppearance.NotificationImportanceButton"
+                        android:text="@string/notification_automatic_title"/>
+                    <TextView
+                        android:id="@+id/automatic_summary"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/notification_importance_button_description_top_margin"
+                        android:visibility="gone"
+                        android:text="@string/notification_channel_summary_automatic"
+                        android:clickable="false"
+                        android:focusable="false"
+                        android:ellipsize="end"
+                        android:maxLines="2"
+                        android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
+                </LinearLayout>
+            </com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
+
+            <com.android.systemui.statusbar.notification.row.ButtonLinearLayout
+                android:id="@+id/alert"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingVertical="@dimen/notification_2025_importance_button_padding_vertical"
+                android:paddingHorizontal="@dimen/notification_2025_importance_button_padding_horizontal"
+                android:gravity="center_vertical"
+                android:clickable="true"
+                android:focusable="true"
+                android:background="@drawable/notification_2025_guts_priority_button_bg"
+                android:orientation="horizontal">
+                <ImageView
+                    android:id="@+id/alert_icon"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:paddingEnd="@*android:dimen/notification_2025_margin"
+                    android:src="@drawable/ic_notifications_alert"
+                    android:background="@android:color/transparent"
+                    android:tint="@color/notification_guts_priority_contents"
+                    android:clickable="false"
+                    android:focusable="false"/>
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="vertical"
+                    android:gravity="center"
+                    >
+                    <TextView
+                        android:id="@+id/alert_label"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="1"
+                        android:ellipsize="end"
+                        android:maxLines="1"
+                        android:clickable="false"
+                        android:focusable="false"
+                        android:textAppearance="@style/TextAppearance.NotificationImportanceButton"
+                        android:text="@string/notification_alert_title"/>
+                    <TextView
+                        android:id="@+id/alert_summary"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:visibility="gone"
+                        android:text="@string/notification_channel_summary_default"
+                        android:clickable="false"
+                        android:focusable="false"
+                        android:ellipsize="end"
+                        android:maxLines="2"
+                        android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
+                </LinearLayout>
+            </com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
+
+            <com.android.systemui.statusbar.notification.row.ButtonLinearLayout
+                android:id="@+id/silence"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/notification_importance_button_separation"
+                android:paddingVertical="@dimen/notification_2025_importance_button_padding_vertical"
+                android:paddingHorizontal="@dimen/notification_2025_importance_button_padding_horizontal"
+                android:gravity="center_vertical"
+                android:clickable="true"
+                android:focusable="true"
+                android:background="@drawable/notification_2025_guts_priority_button_bg"
+                android:orientation="horizontal">
+                <ImageView
+                    android:id="@+id/silence_icon"
+                    android:src="@drawable/ic_notifications_silence"
+                    android:background="@android:color/transparent"
+                    android:tint="@color/notification_guts_priority_contents"
+                    android:layout_gravity="center"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:paddingEnd="@*android:dimen/notification_2025_margin"
+                    android:clickable="false"
+                    android:focusable="false"/>
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="vertical"
+                    android:gravity="center"
+                    >
+                    <TextView
+                        android:id="@+id/silence_label"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:ellipsize="end"
+                        android:maxLines="1"
+                        android:clickable="false"
+                        android:focusable="false"
+                        android:layout_toEndOf="@id/silence_icon"
+                        android:textAppearance="@style/TextAppearance.NotificationImportanceButton"
+                        android:text="@string/notification_silence_title"/>
+                    <TextView
+                        android:id="@+id/silence_summary"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:visibility="gone"
+                        android:text="@string/notification_channel_summary_low"
+                        android:clickable="false"
+                        android:focusable="false"
+                        android:ellipsize="end"
+                        android:maxLines="2"
+                        android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
+                </LinearLayout>
+            </com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/bottom_buttons"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@*android:dimen/notification_2025_margin"
+            android:minHeight="@dimen/notification_2025_guts_button_size"
+            android:gravity="center_vertical"
+            >
+            <TextView
+                android:id="@+id/turn_off_notifications"
+                android:text="@string/inline_turn_off_notifications"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="32dp"
+                android:paddingTop="8dp"
+                android:paddingBottom="@*android:dimen/notification_2025_margin"
+                android:gravity="center"
+                android:minWidth="@dimen/notification_2025_min_tap_target_size"
+                android:minHeight="@dimen/notification_2025_min_tap_target_size"
+                android:maxWidth="200dp"
+                style="@style/TextAppearance.NotificationInfo.Button"
+                android:textSize="@*android:dimen/notification_2025_action_text_size"/>
+            <TextView
+                android:id="@+id/done"
+                android:text="@string/inline_ok_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingTop="8dp"
+                android:paddingBottom="@*android:dimen/notification_2025_margin"
+                android:gravity="center"
+                android:minWidth="@dimen/notification_2025_min_tap_target_size"
+                android:minHeight="@dimen/notification_2025_min_tap_target_size"
+                android:maxWidth="125dp"
+                style="@style/TextAppearance.NotificationInfo.Button"
+                android:textSize="@*android:dimen/notification_2025_action_text_size"/>
+        </LinearLayout>
+    </LinearLayout>
+</com.android.systemui.statusbar.notification.row.NotificationInfo>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 640e1fa..7c370d3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -390,6 +390,12 @@
     <!-- Extra space for guts bundle feedback button -->
     <dimen name="notification_guts_bundle_feedback_size">48dp</dimen>
 
+    <!-- Size of icon buttons in notification info. -->
+    <!-- 24dp for the icon itself + 16dp * 2 for top and bottom padding -->
+    <dimen name="notification_2025_guts_button_size">56dp</dimen>
+
+    <dimen name="notification_2025_min_tap_target_size">48dp</dimen>
+
     <dimen name="notification_importance_toggle_size">48dp</dimen>
     <dimen name="notification_importance_button_separation">8dp</dimen>
     <dimen name="notification_importance_drawable_padding">8dp</dimen>
@@ -402,6 +408,10 @@
     <dimen name="notification_importance_button_description_top_margin">12dp</dimen>
     <dimen name="rect_button_radius">8dp</dimen>
 
+    <!-- Padding for importance selection buttons in notification info, 2025 redesign version -->
+    <dimen name="notification_2025_importance_button_padding_vertical">12dp</dimen>
+    <dimen name="notification_2025_importance_button_padding_horizontal">16dp</dimen>
+
     <!-- The minimum height for the snackbar shown after the snooze option has been chosen. -->
     <dimen name="snooze_snackbar_min_height">56dp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 4a4cb7a..8f8bcf2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -239,7 +239,6 @@
                             R.dimen.keyguard_pattern_activated_dot_size));
             mLockPatternView.setPathWidth(
                     getResources().getDimensionPixelSize(R.dimen.keyguard_pattern_stroke_width));
-            mLockPatternView.setKeepDotActivated(true);
         }
 
         mEcaView = findViewById(R.id.keyguard_selector_fade_container);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 7fb6664..f6df425 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -36,6 +36,7 @@
 import com.android.internal.widget.LockscreenCredential;
 import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.Flags;
 import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer;
 import com.android.systemui.classifier.FalsingClassifier;
 import com.android.systemui.classifier.FalsingCollector;
@@ -237,8 +238,12 @@
         super.onViewAttached();
         mLockPatternView.setOnPatternListener(new UnlockPatternListener());
         mLockPatternView.setSaveEnabled(false);
-        mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
-                mSelectedUserInteractor.getSelectedUserId()));
+        boolean visiblePatternEnabled = mLockPatternUtils.isVisiblePatternEnabled(
+                mSelectedUserInteractor.getSelectedUserId());
+        mLockPatternView.setInStealthMode(!visiblePatternEnabled);
+        if (Flags.bouncerUiRevamp2()) {
+            mLockPatternView.setKeepDotActivated(visiblePatternEnabled);
+        }
         mLockPatternView.setOnTouchListener((v, event) -> {
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                 mFalsingCollector.avoidGesture();
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
index 208adc2..5f7dca8 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
@@ -64,15 +64,14 @@
                         synchronized(sessions) {
                             sessions.putAll(
                                 packageInstaller.allSessions
-                                    .filter { !TextUtils.isEmpty(it.appPackageName) }
-                                    .map { session -> session.toModel() }
+                                    .mapNotNull { session -> session.toModel() }
                                     .associateBy { it.sessionId }
                             )
                             updateInstallerSessionsFlow()
                         }
                         packageInstaller.registerSessionCallback(
                             this@PackageInstallerMonitor,
-                            bgHandler
+                            bgHandler,
                         )
                     } else {
                         synchronized(sessions) {
@@ -130,7 +129,7 @@
             if (session == null) {
                 sessions.remove(sessionId)
             } else {
-                sessions[sessionId] = session.toModel()
+                session.toModel()?.apply { sessions[sessionId] = this }
             }
             updateInstallerSessionsFlow()
         }
@@ -144,7 +143,11 @@
     companion object {
         const val TAG = "PackageInstallerMonitor"
 
-        private fun PackageInstaller.SessionInfo.toModel(): PackageInstallSession {
+        private fun PackageInstaller.SessionInfo.toModel(): PackageInstallSession? {
+            if (TextUtils.isEmpty(this.appPackageName)) {
+                return null
+            }
+
             return PackageInstallSession(
                 sessionId = this.sessionId,
                 packageName = this.appPackageName,
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/colors/SurfaceEffectColors.kt b/packages/SystemUI/src/com/android/systemui/common/shared/colors/SurfaceEffectColors.kt
index 5e8c21f9..4451f07 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/colors/SurfaceEffectColors.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/colors/SurfaceEffectColors.kt
@@ -16,23 +16,27 @@
 
 package com.android.systemui.common.shared.colors
 
-import android.content.res.Resources
+import android.content.Context
 
 object SurfaceEffectColors {
     @JvmStatic
-    fun surfaceEffect0(r: Resources): Int {
-        return r.getColor(com.android.internal.R.color.surface_effect_0)
+    fun surfaceEffect0(context: Context): Int {
+        return context.resources.getColor(
+            com.android.internal.R.color.surface_effect_0, context.theme)
     }
     @JvmStatic
-    fun surfaceEffect1(r: Resources): Int {
-        return r.getColor(com.android.internal.R.color.surface_effect_1)
+    fun surfaceEffect1(context: Context): Int {
+        return context.resources.getColor(
+            com.android.internal.R.color.surface_effect_1, context.theme)
     }
     @JvmStatic
-    fun surfaceEffect2(r: Resources): Int {
-        return r.getColor(com.android.internal.R.color.surface_effect_2)
+    fun surfaceEffect2(context: Context): Int {
+        return context.resources.getColor(
+            com.android.internal.R.color.surface_effect_2, context.theme)
     }
     @JvmStatic
-    fun surfaceEffect3(r: Resources): Int {
-        return r.getColor(com.android.internal.R.color.surface_effect_3)
+    fun surfaceEffect3(context: Context): Int {
+        return context.resources.getColor(
+            com.android.internal.R.color.surface_effect_3, context.theme)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
index 74d471c..e119ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
@@ -101,7 +101,7 @@
                 if (Flags.notificationShadeBlur()) {
                     transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
                 } else {
-                    emptyFlow()
+                    transitionAnimation.immediatelyTransitionTo(blurConfig.minBlurRadiusPx)
                 },
             flowWhenShadeIsNotExpanded =
                 transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt
index 3c126aa..f14a5a2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt
@@ -51,7 +51,7 @@
                 if (Flags.notificationShadeBlur()) {
                     transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
                 } else {
-                    emptyFlow()
+                    transitionAnimation.immediatelyTransitionTo(blurConfig.minBlurRadiusPx)
                 },
             flowWhenShadeIsNotExpanded =
                 transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
index 5a111aa..4a39421 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
@@ -32,7 +32,6 @@
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.emptyFlow
 
 /**
  * Breaks down PRIMARY BOUNCER->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -81,7 +80,7 @@
                 if (Flags.notificationShadeBlur()) {
                     transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
                 } else {
-                    emptyFlow()
+                    transitionAnimation.immediatelyTransitionTo(blurConfig.minBlurRadiusPx)
                 },
             flowWhenShadeIsNotExpanded =
                 transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt
index 0f0e7b6..31b20a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.emptyFlow
 
 @SysUISingleton
 class PrimaryBouncerToOccludedTransitionViewModel
@@ -51,7 +50,7 @@
                 if (Flags.notificationShadeBlur()) {
                     transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
                 } else {
-                    emptyFlow()
+                    transitionAnimation.immediatelyTransitionTo(blurConfig.minBlurRadiusPx)
                 },
             flowWhenShadeIsNotExpanded =
                 transitionAnimation.immediatelyTransitionTo(blurConfig.minBlurRadiusPx),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 609541b..c70a854 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.content.Intent
 import android.os.UserHandle
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Dumpable
 import com.android.systemui.ProtoDumpable
 import com.android.systemui.dagger.SysUISingleton
@@ -62,7 +63,6 @@
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 /**
@@ -245,7 +245,6 @@
                                         processExistingTile(
                                             tileSpec,
                                             specsToTiles.getValue(tileSpec),
-                                            userChanged,
                                             newUser,
                                         ) ?: createTile(tileSpec)
                                     } else {
@@ -378,7 +377,6 @@
     private fun processExistingTile(
         tileSpec: TileSpec,
         tileOrNotInstalled: TileOrNotInstalled,
-        userChanged: Boolean,
         user: Int,
     ): QSTile? {
         return when (tileOrNotInstalled) {
@@ -386,6 +384,10 @@
             is TileOrNotInstalled.Tile -> {
                 val qsTile = tileOrNotInstalled.tile
                 when {
+                    qsTile.isDestroyed -> {
+                        logger.logTileDestroyedIgnored(tileSpec)
+                        null
+                    }
                     !qsTile.isAvailable -> {
                         logger.logTileDestroyed(
                             tileSpec,
@@ -399,10 +401,11 @@
                     qsTile !is CustomTile -> {
                         // The tile is not a custom tile. Make sure they are reset to the correct
                         // user
-                        if (userChanged) {
+                        if (qsTile.currentTileUser != user) {
                             qsTile.userSwitch(user)
                             logger.logTileUserChanged(tileSpec, user)
                         }
+
                         qsTile
                     }
                     qsTile.user == user -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index e237ca9..21a8ec6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -60,7 +60,7 @@
                 bool1 = usesDefault
                 int1 = user
             },
-            { "Parsed tiles (default=$bool1, user=$int1): $str1" }
+            { "Parsed tiles (default=$bool1, user=$int1): $str1" },
         )
     }
 
@@ -77,7 +77,7 @@
                 str2 = reconciledTiles.toString()
                 int1 = user
             },
-            { "Tiles restored and reconciled for user: $int1\nWas: $str1\nSet to: $str2" }
+            { "Tiles restored and reconciled for user: $int1\nWas: $str1\nSet to: $str2" },
         )
     }
 
@@ -94,7 +94,7 @@
                 str2 = newList.toString()
                 int1 = userId
             },
-            { "Processing $str1 for user $int1\nNew list: $str2" }
+            { "Processing $str1 for user $int1\nNew list: $str2" },
         )
     }
 
@@ -107,7 +107,16 @@
                 str1 = spec.toString()
                 str2 = reason.readable
             },
-            { "Tile $str1 destroyed. Reason: $str2" }
+            { "Tile $str1 destroyed. Reason: $str2" },
+        )
+    }
+
+    fun logTileDestroyedIgnored(spec: TileSpec) {
+        tileListLogBuffer.log(
+            TILE_LIST_TAG,
+            LogLevel.DEBUG,
+            { str1 = spec.toString() },
+            { "Tile $str1 ignored as it was already destroyed." },
         )
     }
 
@@ -117,7 +126,7 @@
             TILE_LIST_TAG,
             LogLevel.DEBUG,
             { str1 = spec.toString() },
-            { "Tile $str1 created" }
+            { "Tile $str1 created" },
         )
     }
 
@@ -127,7 +136,7 @@
             TILE_LIST_TAG,
             LogLevel.VERBOSE,
             { str1 = spec.toString() },
-            { "Tile $str1 not found in factory" }
+            { "Tile $str1 not found in factory" },
         )
     }
 
@@ -140,7 +149,7 @@
                 str1 = spec.toString()
                 int1 = user
             },
-            { "User changed to $int1 for tile $str1" }
+            { "User changed to $int1 for tile $str1" },
         )
     }
 
@@ -156,7 +165,7 @@
                 str1 = tiles.toString()
                 int1 = user
             },
-            { "Tiles kept for not installed packages for user $int1: $str1" }
+            { "Tiles kept for not installed packages for user $int1: $str1" },
         )
     }
 
@@ -168,7 +177,7 @@
                 str1 = tiles.toString()
                 int1 = userId
             },
-            { "Auto add tiles parsed for user $int1: $str1" }
+            { "Auto add tiles parsed for user $int1: $str1" },
         )
     }
 
@@ -180,7 +189,7 @@
                 str1 = tiles.toString()
                 int1 = userId
             },
-            { "Auto-add tiles reconciled for user $int1: $str1" }
+            { "Auto-add tiles reconciled for user $int1: $str1" },
         )
     }
 
@@ -193,7 +202,7 @@
                 int2 = position
                 str1 = spec.toString()
             },
-            { "Tile $str1 auto added for user $int1 at position $int2" }
+            { "Tile $str1 auto added for user $int1 at position $int2" },
         )
     }
 
@@ -205,7 +214,7 @@
                 int1 = userId
                 str1 = spec.toString()
             },
-            { "Tile $str1 auto removed for user $int1" }
+            { "Tile $str1 auto removed for user $int1" },
         )
     }
 
@@ -217,7 +226,7 @@
                 int1 = userId
                 str1 = spec.toString()
             },
-            { "Tile $str1 unmarked as auto-added for user $int1" }
+            { "Tile $str1 unmarked as auto-added for user $int1" },
         )
     }
 
@@ -226,7 +235,7 @@
             RESTORE_TAG,
             LogLevel.DEBUG,
             { int1 = userId },
-            { "Restored from single intent after user setup complete for user $int1" }
+            { "Restored from single intent after user setup complete for user $int1" },
         )
     }
 
@@ -243,7 +252,7 @@
                 "Restored settings data for user $int1\n" +
                     "\tRestored tiles: $str1\n" +
                     "\tRestored auto added tiles: $str2"
-            }
+            },
         )
     }
 
@@ -258,7 +267,7 @@
                 str1 = restoreProcessorClassName
                 str2 = step.name
             },
-            { "Restore $str2 processed by $str1" }
+            { "Restore $str2 processed by $str1" },
         )
     }
 
@@ -273,6 +282,6 @@
 
     enum class RestorePreprocessorStep {
         PREPROCESSING,
-        POSTPROCESSING
+        POSTPROCESSING,
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 1d1e991..c6fc868 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -74,6 +74,8 @@
 
 import java.io.PrintWriter;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Base quick-settings tile, extend this to create a new tile.
@@ -127,6 +129,8 @@
     private int mIsFullQs;
 
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
+    private final AtomicBoolean mIsDestroyed = new AtomicBoolean(false);
+    private final AtomicInteger mCurrentTileUser = new AtomicInteger();
 
     /**
      * Provides a new {@link TState} of the appropriate type to use between this tile and the
@@ -203,6 +207,7 @@
         mMetricsLogger = metricsLogger;
         mStatusBarStateController = statusBarStateController;
         mActivityStarter = activityStarter;
+        mCurrentTileUser.set(host.getUserId());
 
         resetStates();
         mUiHandler.post(() -> mLifecycle.setCurrentState(CREATED));
@@ -352,11 +357,19 @@
     }
 
     public void userSwitch(int newUserId) {
+        mCurrentTileUser.set(newUserId);
         mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget();
         postStale();
     }
 
+    @Override
+    public int getCurrentTileUser() {
+        return mCurrentTileUser.get();
+    }
+
     public void destroy() {
+        // We mark it as soon as we start the destroy process, as nothing can interrupt it.
+        mIsDestroyed.set(true);
         mHandler.sendEmptyMessage(H.DESTROY);
     }
 
@@ -365,7 +378,7 @@
      *
      * Should be called upon creation of the tile, before performing other operations
      */
-    public void initialize() {
+    public final void initialize() {
         mHandler.sendEmptyMessage(H.INITIALIZE);
     }
 
@@ -525,6 +538,11 @@
         });
     }
 
+    @Override
+    public final boolean isDestroyed() {
+        return mIsDestroyed.get();
+    }
+
     protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) {
         EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
                 userRestriction, mHost.getUserId());
@@ -799,7 +817,7 @@
      */
     @Override
     public void dump(PrintWriter pw, String[] args) {
-        pw.println(this.getClass().getSimpleName() + ":");
+        pw.print(this.getClass().getSimpleName() + ":");
         pw.print("    "); pw.println(getState().toString());
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
index f80b8fb..e48e943 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
@@ -99,7 +99,7 @@
     }
 
     override fun getDetailsViewModel(): TileDetailsViewModel {
-        return internetDetailsViewModelFactory.create { longClick(null) }
+        return internetDetailsViewModelFactory.create()
     }
 
     override fun handleUpdateState(state: QSTile.BooleanState, arg: Any?) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
index e8c4274..8ad4e16 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
@@ -28,17 +28,4 @@
      * It's safe to run long running computations inside this function.
      */
     @WorkerThread suspend fun handleInput(input: QSTileInput<DATA_TYPE>)
-
-    /**
-     * Provides the [TileDetailsViewModel] for constructing the corresponding details view.
-     *
-     * This property is defined here to reuse the business logic. For example, reusing the user
-     * long-click as the go-to-settings callback in the details view.
-     * Subclasses can override this property to provide a specific [TileDetailsViewModel]
-     * implementation.
-     *
-     * @return The [TileDetailsViewModel] instance, or null if not implemented.
-     */
-    val detailsViewModel: TileDetailsViewModel?
-        get() = null
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
index 8c75cf0..7f475f3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.qs.TileDetailsViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
 import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
@@ -70,9 +71,7 @@
          * Creates [QSTileViewModelImpl] based on the interactors obtained from [QSTileComponent].
          * Reference of that [QSTileComponent] is then stored along the view model.
          */
-        fun create(
-            tileSpec: TileSpec,
-        ): QSTileViewModel {
+        fun create(tileSpec: TileSpec): QSTileViewModel {
             val config = qsTileConfigProvider.getConfig(tileSpec.spec)
             val component =
                 customTileComponentBuilder.qsTileConfigModule(QSTileConfigModule(config)).build()
@@ -90,6 +89,7 @@
                 backgroundDispatcher,
                 uiBackgroundDispatcher,
                 component.coroutineScope(),
+                /* tileDetailsViewModel= */ null,
             )
         }
     }
@@ -127,6 +127,7 @@
             userActionInteractor: QSTileUserActionInteractor<T>,
             tileDataInteractor: QSTileDataInteractor<T>,
             mapper: QSTileDataToStateMapper<T>,
+            tileDetailsViewModel: TileDetailsViewModel? = null,
         ): QSTileViewModelImpl<T> =
             QSTileViewModelImpl(
                 qsTileConfigProvider.getConfig(tileSpec.spec),
@@ -142,6 +143,7 @@
                 backgroundDispatcher,
                 uiBackgroundDispatcher,
                 coroutineScopeFactory.create(),
+                tileDetailsViewModel,
             )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
index 30bf5b3..3866c17 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
@@ -83,6 +83,7 @@
     private val backgroundDispatcher: CoroutineDispatcher,
     uiBackgroundDispatcher: CoroutineDispatcher,
     private val tileScope: CoroutineScope,
+    override val tileDetailsViewModel: TileDetailsViewModel? = null,
 ) : QSTileViewModel, Dumpable {
 
     private val users: MutableStateFlow<UserHandle> =
@@ -96,6 +97,9 @@
 
     private val tileData: SharedFlow<DATA_TYPE?> = createTileDataFlow()
 
+    override val currentTileUser: Int
+        get() = users.value.identifier
+
     override val state: StateFlow<QSTileState?> =
         tileData
             .map { data ->
@@ -114,9 +118,6 @@
             .flowOn(backgroundDispatcher)
             .stateIn(tileScope, SharingStarted.WhileSubscribed(), true)
 
-    override val detailsViewModel: TileDetailsViewModel?
-        get() = userActionInteractor().detailsViewModel
-
     override fun forceUpdate() {
         tileScope.launch(context = backgroundDispatcher) { forceUpdates.emit(Unit) }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt
index 0ed56f6..6709fd2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt
@@ -16,9 +16,11 @@
 
 package com.android.systemui.qs.tiles.dialog
 
+import android.content.Intent
+import android.provider.Settings
 import com.android.systemui.plugins.qs.TileDetailsViewModel
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
 import com.android.systemui.statusbar.connectivity.AccessPointController
-import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 
@@ -27,10 +29,13 @@
 constructor(
     private val accessPointController: AccessPointController,
     val contentManagerFactory: InternetDetailsContentManager.Factory,
-    @Assisted private val onLongClick: () -> Unit,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
 ) : TileDetailsViewModel() {
     override fun clickOnSettingsButton() {
-        onLongClick()
+        qsTileIntentUserActionHandler.handle(
+            /* expandable= */ null,
+            Intent(Settings.ACTION_WIFI_SETTINGS),
+        )
     }
 
     override fun getTitle(): String {
@@ -58,7 +63,7 @@
     }
 
     @AssistedFactory
-    interface Factory {
-        fun create(onLongClick: () -> Unit): InternetDetailsViewModel
+    fun interface Factory {
+        fun create(): InternetDetailsViewModel
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileComponent.kt
index 0ed46e7..5f692f2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileComponent.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.impl.di
 
+import com.android.systemui.plugins.qs.TileDetailsViewModel
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
index 8e48fe4..0431e36 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
@@ -18,13 +18,10 @@
 
 import android.content.Intent
 import android.provider.Settings
-import com.android.systemui.animation.Expandable
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.plugins.qs.TileDetailsViewModel
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.base.interactor.QSTileInput
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
-import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel
 import com.android.systemui.qs.tiles.dialog.InternetDialogManager
 import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
@@ -41,7 +38,6 @@
     private val internetDialogManager: InternetDialogManager,
     private val accessPointController: AccessPointController,
     private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
-    private val internetDetailsViewModelFactory: InternetDetailsViewModel.Factory,
 ) : QSTileUserActionInteractor<InternetTileModel> {
 
     override suspend fun handleInput(input: QSTileInput<InternetTileModel>): Unit =
@@ -58,16 +54,12 @@
                     }
                 }
                 is QSTileUserAction.LongClick -> {
-                    handleLongClick(action.expandable)
+                    qsTileIntentUserActionHandler.handle(
+                        action.expandable,
+                        Intent(Settings.ACTION_WIFI_SETTINGS),
+                    )
                 }
                 else -> {}
             }
         }
-
-    override val detailsViewModel: TileDetailsViewModel =
-        internetDetailsViewModelFactory.create { handleLongClick(null) }
-
-    private fun handleLongClick(expandable: Expandable?) {
-        qsTileIntentUserActionHandler.handle(expandable, Intent(Settings.ACTION_WIFI_SETTINGS))
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
index e8b9926..7a53388 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
@@ -39,9 +39,12 @@
     val isAvailable: StateFlow<Boolean>
 
     /** Specifies the [TileDetailsViewModel] for constructing the corresponding details view. */
-    val detailsViewModel: TileDetailsViewModel?
+    val tileDetailsViewModel: TileDetailsViewModel?
         get() = null
 
+    /** Returns the current user for this tile */
+    val currentTileUser: Int
+
     /**
      * Notifies about the user change. Implementations should avoid using 3rd party userId sources
      * and use this value instead. This is to maintain consistent and concurrency-free behaviour
@@ -65,8 +68,6 @@
     fun destroy()
 }
 
-/**
- * Returns the immediate state of the tile or null if the state haven't been collected yet.
- */
+/** Returns the immediate state of the tile or null if the state haven't been collected yet. */
 val QSTileViewModel.currentState: QSTileState?
     get() = state.value
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index 30d1f05..e607eae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -19,7 +19,6 @@
 import android.content.Context
 import android.os.UserHandle
 import android.util.Log
-import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.logging.InstanceId
 import com.android.systemui.Dumpable
 import com.android.systemui.animation.Expandable
@@ -156,8 +155,12 @@
         qsTileViewModel.onUserChanged(UserHandle.of(currentUser))
     }
 
+    override fun getCurrentTileUser(): Int {
+        return qsTileViewModel.currentTileUser
+    }
+
     override fun getDetailsViewModel(): TileDetailsViewModel? {
-        return qsTileViewModel.detailsViewModel
+        return qsTileViewModel.tileDetailsViewModel
     }
 
     @Deprecated(
@@ -213,6 +216,10 @@
         qsTileViewModel.destroy()
     }
 
+    override fun isDestroyed(): Boolean {
+        return !(tileAdapterJob?.isActive ?: false)
+    }
+
     override fun getState(): QSTile.AdapterState =
         qsTileViewModel.currentState?.let { mapState(context, it, qsTileViewModel.config) }
             ?: QSTile.AdapterState()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt
index 00b7e61..bdd5c73 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt
@@ -37,4 +37,7 @@
     override fun onActionPerformed(userAction: QSTileUserAction) = error("Don't call stubs")
 
     override fun destroy() = error("Don't call stubs")
+
+    override val currentTileUser: Int
+        get() = error("Don't call stubs")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 13737dbff..934404d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -391,7 +392,9 @@
             }
             if (view is ExpandableNotificationRow) {
                 // Only drag down on sensitive views, otherwise the ExpandHelper will take this
-                return view.entry.isSensitive.value
+                return if (NotificationBundleUi.isEnabled)
+                    view.entryAdapter?.isSensitive?.value == true
+                else view.entry.isSensitive.value
             }
         }
         return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index 10090283..48f0245 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.qs.tiles.NfcTile
 import com.android.systemui.qs.tiles.base.interactor.QSTileAvailabilityInteractor
 import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel
 import com.android.systemui.qs.tiles.impl.airplane.domain.AirplaneModeMapper
 import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor
 import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor
@@ -162,13 +163,15 @@
             factory: QSTileViewModelFactory.Static<AirplaneModeTileModel>,
             mapper: AirplaneModeMapper,
             stateInteractor: AirplaneModeTileDataInteractor,
-            userActionInteractor: AirplaneModeTileUserActionInteractor
+            userActionInteractor: AirplaneModeTileUserActionInteractor,
+            internetDetailsViewModelFactory: InternetDetailsViewModel.Factory
         ): QSTileViewModel =
             factory.create(
                 TileSpec.create(AIRPLANE_MODE_TILE_SPEC),
                 userActionInteractor,
                 stateInteractor,
                 mapper,
+                internetDetailsViewModelFactory.create(),
             )
 
         @Provides
@@ -226,13 +229,15 @@
             factory: QSTileViewModelFactory.Static<InternetTileModel>,
             mapper: InternetTileMapper,
             stateInteractor: InternetTileDataInteractor,
-            userActionInteractor: InternetTileUserActionInteractor
+            userActionInteractor: InternetTileUserActionInteractor,
+            internetDetailsViewModelFactory: InternetDetailsViewModel.Factory
         ): QSTileViewModel =
             factory.create(
                 TileSpec.create(INTERNET_TILE_SPEC),
                 userActionInteractor,
                 stateInteractor,
                 mapper,
+                internetDetailsViewModelFactory.create(),
             )
 
         @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
index 243a868..874a059 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import kotlin.math.ceil
 import kotlin.math.max
@@ -117,7 +118,7 @@
         params.startNotificationTop = location[1]
         params.notificationParentTop =
             notificationListContainer
-                .getViewParentForNotification(notificationEntry)
+                .getViewParentForNotification()
                 .locationOnScreen[1]
         params.startRoundedTopClipping = roundedTopClipping
         params.startClipTopAmount = notification.clipTopAmount
@@ -148,7 +149,7 @@
             Log.d(TAG, reason)
         }
         notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(willAnimate)
-        notificationEntry.isExpandAnimationRunning = willAnimate
+        notification.isLaunchAnimationRunning = willAnimate
 
         if (!willAnimate) {
             removeHun(animate = true, reason)
@@ -158,7 +159,8 @@
 
     private val headsUpNotificationRow: ExpandableNotificationRow?
         get() {
-            val pipelineParent = notificationEntry.parent
+            val pipelineParent = if (NotificationBundleUi.isEnabled)
+                notification.entryAdapter?.parent else notificationEntry.parent
             val summaryEntry = (pipelineParent as? GroupEntry)?.summary
             return when {
                 headsUpManager.isHeadsUpEntry(notificationKey) -> notification
@@ -190,7 +192,7 @@
         // TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started
         // here?
         notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false)
-        notificationEntry.isExpandAnimationRunning = false
+        notification.isLaunchAnimationRunning = false
         removeHun(animate = true, "onLaunchAnimationCancelled()")
         onFinishAnimationCallback?.run()
     }
@@ -210,7 +212,7 @@
 
         notification.isExpandAnimationRunning = false
         notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false)
-        notificationEntry.isExpandAnimationRunning = false
+        notification.isLaunchAnimationRunning = false
         notificationListContainer.setExpandingNotification(null)
         applyParams(null)
         removeHun(animate = false, "onLaunchAnimationEnd()")
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 9ed1632..c2f0806 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
@@ -136,9 +136,7 @@
             @NonNull NotifInflater.Params params,
             NotificationRowContentBinder.InflationCallback callback)
             throws InflationException {
-        //TODO(b/217799515): Remove the entry parameter from getViewParentForNotification(), this
-        // function returns the NotificationStackScrollLayout regardless of the entry.
-        ViewGroup parent = mListContainer.getViewParentForNotification(entry);
+        ViewGroup parent = mListContainer.getViewParentForNotification();
 
         if (entry.rowExists()) {
             mLogger.logUpdatingRow(entry, params);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 25deec3..d09546f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -391,7 +391,7 @@
         if (!notificationFooterBackgroundTintOptimization()) {
             if (notificationShadeBlur()) {
                 Color backgroundColor = Color.valueOf(
-                        SurfaceEffectColors.surfaceEffect1(getResources()));
+                        SurfaceEffectColors.surfaceEffect1(getContext()));
                 scHigh = ColorUtils.setAlphaComponent(backgroundColor.toArgb(), 0xFF);
                 // Apply alpha on background drawables.
                 int backgroundAlpha = (int) (backgroundColor.alpha() * 0xFF);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
index 7c5f3b5..5157e7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
@@ -332,7 +332,9 @@
             onEntryAdded(headsUpEntry, requestedPinnedStatus);
             // TODO(b/328390331) move accessibility events to the view layer
             entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-            entry.setIsHeadsUpEntry(true);
+            if (!NotificationBundleUi.isEnabled()) {
+                entry.setIsHeadsUpEntry(true);
+            }
 
             updateNotificationInternal(entry.getKey(), requestedPinnedStatus);
             entry.setInterruption();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 4ed9dce..a081ad5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -129,7 +129,7 @@
 
     private void updateColors() {
         if (notificationRowTransparency()) {
-            mNormalColor = SurfaceEffectColors.surfaceEffect1(getResources());
+            mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext());
         } else {
             mNormalColor = mContext.getColor(
                     com.android.internal.R.color.materialColorSurfaceContainerHigh);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 6134d1d..185e7fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -300,6 +300,7 @@
     private boolean mIsSystemChildExpanded;
     private PinnedStatus mPinnedStatus = PinnedStatus.NotPinned;
     private boolean mExpandAnimationRunning;
+    private boolean mLaunchAnimationRunning;
     private AboveShelfChangedListener mAboveShelfChangedListener;
     private HeadsUpManager mHeadsUpManager;
     private Consumer<Boolean> mHeadsUpAnimatingAwayListener;
@@ -4487,4 +4488,22 @@
         }
         mLogger.logRemoveTransientRow(row.getLoggingKey(), mLoggingKey);
     }
+
+    /** Set whether this notification is currently used to animate a launch. */
+    public void setLaunchAnimationRunning(boolean launchAnimationRunning) {
+        if (NotificationBundleUi.isEnabled()) {
+            mLaunchAnimationRunning = launchAnimationRunning;
+        } else {
+            getEntry().setExpandAnimationRunning(launchAnimationRunning);
+        }
+    }
+
+    /** Whether this notification is currently used to animate a launch. */
+    public boolean isLaunchAnimationRunning() {
+        if (NotificationBundleUi.isEnabled()) {
+            return mLaunchAnimationRunning;
+        } else {
+            return getEntry().isExpandAnimationRunning();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index 33c36d8c..c0bc132 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -88,7 +88,7 @@
         mDarkColoredStatefulColors = getResources().getColorStateList(
                 R.color.notification_state_color_dark);
         if (notificationRowTransparency()) {
-            mNormalColor = SurfaceEffectColors.surfaceEffect1(getResources());
+            mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext());
         } else  {
             mNormalColor = mContext.getColor(
                     com.android.internal.R.color.materialColorSurfaceContainerHigh);
@@ -321,7 +321,7 @@
                 new PorterDuffColorFilter(
                         isColorized()
                                 ? ColorUtils.setAlphaComponent(mTintColor, (int) (255 * 0.9f))
-                                : SurfaceEffectColors.surfaceEffect1(getResources()),
+                                : SurfaceEffectColors.surfaceEffect1(getContext()),
                         PorterDuff.Mode.SRC)); // SRC operator discards the drawable's color+alpha
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index ab382df..e89a76f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS;
+import static android.app.Flags.notificationsRedesignTemplates;
 import static android.view.HapticFeedbackConstants.CLOCK_TICK;
 
 import static com.android.systemui.SwipeHelper.SWIPED_FAR_ENOUGH_SIZE_FRACTION;
@@ -706,8 +706,11 @@
     static NotificationMenuItem createInfoItem(Context context) {
         Resources res = context.getResources();
         String infoDescription = res.getString(R.string.notification_menu_gear_description);
+        int layoutId = notificationsRedesignTemplates()
+                ? R.layout.notification_2025_info
+                : R.layout.notification_info;
         NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate(
-                R.layout.notification_info, null, false);
+                layoutId, null, false);
         return new NotificationMenuItem(context, infoDescription, infoContent,
                 R.drawable.ic_settings);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 64ca815..1e24952 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -35,8 +35,10 @@
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.BypassController;
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -60,6 +62,7 @@
     private final BypassController mBypassController;
     private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
     private final AvalancheController mAvalancheController;
+    private final HeadsUpRepository mHeadsUpRepository;
 
     /**
      *  Used to read bouncer states.
@@ -304,6 +307,7 @@
             @NonNull BypassController bypassController,
             @Nullable StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             @NonNull LargeScreenShadeInterpolator largeScreenShadeInterpolator,
+            @NonNull HeadsUpRepository headsUpRepository,
             AvalancheController avalancheController
     ) {
         mSectionProvider = sectionProvider;
@@ -311,6 +315,7 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
         mAvalancheController = avalancheController;
+        mHeadsUpRepository = headsUpRepository;
         reload(context);
         dumpManager.registerDumpable(this);
     }
@@ -690,7 +695,10 @@
     }
 
     public boolean isPulsing(NotificationEntry entry) {
-        return mPulsing && entry.isHeadsUpEntry();
+        boolean isHeadsUp = NotificationBundleUi.isEnabled()
+                ? mHeadsUpRepository.isHeadsUpEntry(entry.getKey())
+                : entry.isHeadsUpEntry();
+        return mPulsing && isHeadsUp;
     }
 
     public void setPulsingRow(ExpandableNotificationRow row) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
index da98858..9bd5a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
@@ -291,7 +291,7 @@
          * currently being swiped. From the center outwards, the multipliers apply to the neighbors
          * of the swiped view.
          */
-        private val MAGNETIC_TRANSLATION_MULTIPLIERS = listOf(0.18f, 0.28f, 0.5f, 0.28f, 0.18f)
+        private val MAGNETIC_TRANSLATION_MULTIPLIERS = listOf(0.04f, 0.12f, 0.5f, 0.12f, 0.04f)
 
         const val MAGNETIC_REDUCTION = 0.65f
 
@@ -299,7 +299,7 @@
         private const val DETACH_STIFFNESS = 800f
         private const val DETACH_DAMPING_RATIO = 0.95f
         private const val SNAP_BACK_STIFFNESS = 550f
-        private const val SNAP_BACK_DAMPING_RATIO = 0.52f
+        private const val SNAP_BACK_DAMPING_RATIO = 0.6f
 
         // Maximum value of corner roundness that gets applied during the pre-detach dragging
         private const val MAX_PRE_DETACH_ROUNDNESS = 0.8f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
index f85545e..47fc2fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
@@ -113,10 +113,9 @@
     /**
      * Get the view parent for a notification entry. For example, NotificationStackScrollLayout.
      *
-     * @param entry entry to get the view parent for
      * @return the view parent for entry
      */
-    ViewGroup getViewParentForNotification(NotificationEntry entry);
+    ViewGroup getViewParentForNotification();
 
     /**
      * Resets the currently exposed menu view.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 6313258..e7a77eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -2134,7 +2134,7 @@
         }
     }
 
-    public ViewGroup getViewParentForNotification(NotificationEntry entry) {
+    public ViewGroup getViewParentForNotification() {
         return this;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 5c96470..4459430 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1908,8 +1908,8 @@
         }
 
         @Override
-        public ViewGroup getViewParentForNotification(NotificationEntry entry) {
-            return mView.getViewParentForNotification(entry);
+        public ViewGroup getViewParentForNotification() {
+            return mView.getViewParentForNotification();
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 955de273..7101df1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -85,10 +85,12 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
 import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView;
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
 import com.android.systemui.statusbar.notification.headsup.AvalancheController;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
@@ -157,6 +159,7 @@
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
     @Mock private AvalancheController mAvalancheController;
+    @Mock private HeadsUpRepository mHeadsUpRepository;
 
     public NotificationStackScrollLayoutTest(FlagsParameterization flags) {
         super();
@@ -176,6 +179,7 @@
                 mBypassController,
                 mStatusBarKeyguardViewManager,
                 mLargeScreenShadeInterpolator,
+                mHeadsUpRepository,
                 mAvalancheController
         ));
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
index 4714969..c7ea6db 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
@@ -22,7 +22,7 @@
 
 class FakeQSTile(var user: Int, var available: Boolean = true) : QSTile {
     private var tileSpec: String? = null
-    var destroyed = false
+    private var destroyed = false
     var hasDetailsViewModel: Boolean = true
     private var state = QSTile.State()
     val callbacks = mutableListOf<QSTile.Callback>()
@@ -64,6 +64,10 @@
         user = currentUser
     }
 
+    override fun getCurrentTileUser(): Int {
+        return user
+    }
+
     override fun getMetricsCategory(): Int {
         return 0
     }
@@ -76,6 +80,10 @@
         destroyed = true
     }
 
+    override fun isDestroyed(): Boolean {
+        return destroyed
+    }
+
     override fun getTileLabel(): CharSequence {
         return ""
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt
index 4978558..f038fdd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.fakeSystemClock
 
-val Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by
+var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by
     Kosmos.Fixture {
         TileLifecycleManager.Factory { intent, userHandle ->
             TileLifecycleManager(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt
index 7d52f5d..c153183 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt
@@ -17,7 +17,14 @@
 package com.android.systemui.qs.pipeline.shared.logging
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.log.logcatLogBuffer
 
 /** mock */
-var Kosmos.qsLogger: QSPipelineLogger by Kosmos.Fixture { mock<QSPipelineLogger>() }
+var Kosmos.qsLogger: QSPipelineLogger by
+    Kosmos.Fixture {
+        QSPipelineLogger(
+            logcatLogBuffer(QSPipelineLogger.TILE_LIST_TAG),
+            logcatLogBuffer(QSPipelineLogger.AUTO_ADD_TAG),
+            logcatLogBuffer(QSPipelineLogger.RESTORE_TAG),
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
index bc1c60c..c058490 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
@@ -33,7 +33,4 @@
     override suspend fun handleInput(input: QSTileInput<T>) {
         mutex.withLock { mutableInputs.add(input) }
     }
-
-    override var detailsViewModel: TileDetailsViewModel? =
-        FakeTileDetailsViewModel("FakeQSTileUserActionInteractor")
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
index 6787b8e..c223be4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
@@ -19,6 +19,7 @@
 import android.os.UserHandle
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.instanceIdSequenceFake
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
@@ -56,7 +57,11 @@
                     override val config: QSTileConfig = config
                     override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true)
 
-                    override fun onUserChanged(user: UserHandle) {}
+                    override var currentTileUser = currentTilesInteractor.userId.value
+
+                    override fun onUserChanged(user: UserHandle) {
+                        currentTileUser = user.identifier
+                    }
 
                     override fun forceUpdate() {}
 
@@ -68,7 +73,7 @@
         }
     }
 
-val Kosmos.newQSTileFactory by
+var Kosmos.newQSTileFactory by
     Kosmos.Fixture {
         NewQSTileFactory(
             qSTileConfigProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt
index d65a4a0..4f1bf95 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.shade.transition.largeScreenShadeInterpolator
 import com.android.systemui.statusbar.notification.headsup.mockAvalancheController
+import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
 import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
 
 val Kosmos.ambientState by Fixture {
@@ -32,6 +33,7 @@
         /*bypassController=*/ stackScrollAlgorithmBypassController,
         /*statusBarKeyguardViewManager=*/ statusBarKeyguardViewManager,
         /*largeScreenShadeInterpolator=*/ largeScreenShadeInterpolator,
+        /*headsUpRepository=*/ headsUpNotificationRepository,
         /*avalancheController=*/ mockAvalancheController,
     )
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 4b04248..47aa8f5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -1115,14 +1115,12 @@
         if (svcConnTracingEnabled()) {
             logTraceSvcConn("performGlobalAction", "action=" + action);
         }
-        int currentUserId;
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return false;
             }
-            currentUserId = mSystemSupport.getCurrentUserIdLocked();
         }
-        enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
+        enforceCurrentUserIfVisibleBackgroundEnabled();
         final long identity = Binder.clearCallingIdentity();
         try {
             return mSystemActionPerformer.performSystemAction(action);
@@ -2791,11 +2789,7 @@
     @RequiresNoPermission
     @Override
     public void setAnimationScale(float scale) {
-        int currentUserId;
-        synchronized (mLock) {
-            currentUserId = mSystemSupport.getCurrentUserIdLocked();
-        }
-        enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
+        enforceCurrentUserIfVisibleBackgroundEnabled();
         final long identity = Binder.clearCallingIdentity();
         try {
             Settings.Global.putFloat(
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 9eb8442..1c95184 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1402,11 +1402,7 @@
     @EnforcePermission(MANAGE_ACCESSIBILITY)
     public void registerSystemAction(RemoteAction action, int actionId) {
         registerSystemAction_enforcePermission();
-        int currentUserId;
-        synchronized (mLock) {
-            currentUserId = mCurrentUserId;
-        }
-        enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
+        enforceCurrentUserIfVisibleBackgroundEnabled();
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".registerSystemAction",
                     FLAGS_ACCESSIBILITY_MANAGER, "action=" + action + ";actionId=" + actionId);
@@ -1423,11 +1419,7 @@
     @EnforcePermission(MANAGE_ACCESSIBILITY)
     public void unregisterSystemAction(int actionId) {
         unregisterSystemAction_enforcePermission();
-        int currentUserId;
-        synchronized (mLock) {
-            currentUserId = mCurrentUserId;
-        }
-        enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
+        enforceCurrentUserIfVisibleBackgroundEnabled();
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".unregisterSystemAction",
                     FLAGS_ACCESSIBILITY_MANAGER, "actionId=" + actionId);
@@ -1759,7 +1751,7 @@
         synchronized (mLock) {
             currentUserId = mCurrentUserId;
         }
-        enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
+        enforceCurrentUserIfVisibleBackgroundEnabled();
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonClicked",
                     FLAGS_ACCESSIBILITY_MANAGER,
@@ -1807,11 +1799,7 @@
     @EnforcePermission(STATUS_BAR_SERVICE)
     public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
         notifyAccessibilityButtonVisibilityChanged_enforcePermission();
-        int currentUserId;
-        synchronized (mLock) {
-            currentUserId = mCurrentUserId;
-        }
-        enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
+        enforceCurrentUserIfVisibleBackgroundEnabled();
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonVisibilityChanged",
                     FLAGS_ACCESSIBILITY_MANAGER, "shown=" + shown);
@@ -5002,11 +4990,7 @@
             throws RemoteException {
         registerProxyForDisplay_enforcePermission();
         mSecurityPolicy.checkForAccessibilityPermissionOrRole();
-        int currentUserId;
-        synchronized (mLock) {
-            currentUserId = mCurrentUserId;
-        }
-        enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
+        enforceCurrentUserIfVisibleBackgroundEnabled();
         if (client == null) {
             return false;
         }
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index bd34f33..c182c26 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -149,7 +149,6 @@
 
     OperationStorage mOperationStorage;
     List<PackageInfo> mPackages;
-    PackageInfo mCurrentPackage;
     boolean mUpdateSchedule;
     CountDownLatch mLatch;
     FullBackupJob mJob;             // if a scheduled job needs to be finished afterwards
@@ -207,10 +206,9 @@
         for (String pkg : whichPackages) {
             try {
                 PackageManager pm = backupManagerService.getPackageManager();
-                PackageInfo info = pm.getPackageInfoAsUser(pkg,
+                PackageInfo packageInfo = pm.getPackageInfoAsUser(pkg,
                         PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
-                mCurrentPackage = info;
-                if (!mBackupEligibilityRules.appIsEligibleForBackup(info.applicationInfo)) {
+                if (!mBackupEligibilityRules.appIsEligibleForBackup(packageInfo.applicationInfo)) {
                     // Cull any packages that have indicated that backups are not permitted,
                     // that run as system-domain uids but do not define their own backup agents,
                     // as well as any explicit mention of the 'special' shared-storage agent
@@ -220,13 +218,13 @@
                     }
                     mBackupManagerMonitorEventSender.monitorEvent(
                             BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_INELIGIBLE,
-                            mCurrentPackage,
+                            packageInfo,
                             BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                            null);
+                            /* extras= */ null);
                     BackupObserverUtils.sendBackupOnPackageResult(mBackupObserver, pkg,
                             BackupManager.ERROR_BACKUP_NOT_ALLOWED);
                     continue;
-                } else if (!mBackupEligibilityRules.appGetsFullBackup(info)) {
+                } else if (!mBackupEligibilityRules.appGetsFullBackup(packageInfo)) {
                     // Cull any packages that are found in the queue but now aren't supposed
                     // to get full-data backup operations.
                     if (DEBUG) {
@@ -235,13 +233,13 @@
                     }
                     mBackupManagerMonitorEventSender.monitorEvent(
                             BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_KEY_VALUE_PARTICIPANT,
-                            mCurrentPackage,
+                            packageInfo,
                             BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                            null);
+                            /* extras= */ null);
                     BackupObserverUtils.sendBackupOnPackageResult(mBackupObserver, pkg,
                             BackupManager.ERROR_BACKUP_NOT_ALLOWED);
                     continue;
-                } else if (mBackupEligibilityRules.appIsStopped(info.applicationInfo)) {
+                } else if (mBackupEligibilityRules.appIsStopped(packageInfo.applicationInfo)) {
                     // Cull any packages in the 'stopped' state: they've either just been
                     // installed or have explicitly been force-stopped by the user.  In both
                     // cases we do not want to launch them for backup.
@@ -250,21 +248,21 @@
                     }
                     mBackupManagerMonitorEventSender.monitorEvent(
                             BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_STOPPED,
-                            mCurrentPackage,
+                            packageInfo,
                             BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                            null);
+                            /* extras= */ null);
                     BackupObserverUtils.sendBackupOnPackageResult(mBackupObserver, pkg,
                             BackupManager.ERROR_BACKUP_NOT_ALLOWED);
                     continue;
                 }
-                mPackages.add(info);
+                mPackages.add(packageInfo);
             } catch (NameNotFoundException e) {
                 Slog.i(TAG, "Requested package " + pkg + " not found; ignoring");
                 mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_FOUND,
-                        mCurrentPackage,
+                        /* pkg= */ null,
                         BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                        null);
+                        /* extras= */ null);
             }
         }
 
@@ -352,10 +350,11 @@
                 } else {
                     monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED;
                 }
-                mBackupManagerMonitorEventSender
-                        .monitorEvent(monitoringEvent, null,
-                                BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                                null);
+                mBackupManagerMonitorEventSender.monitorEvent(
+                        monitoringEvent,
+                        /* pkg= */ null,
+                        BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+                        /* extras= */ null);
                 mUpdateSchedule = false;
                 backupRunStatus = BackupManager.ERROR_BACKUP_NOT_ALLOWED;
                 return;
@@ -367,8 +366,9 @@
                 backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
                 mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT,
-                        mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
-                        null);
+                        /* pkg= */ null,
+                        BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
+                        /* extras= */ null);
                 return;
             }
 
@@ -461,9 +461,10 @@
                         }
                         mBackupManagerMonitorEventSender.monitorEvent(
                                 BackupManagerMonitor.LOG_EVENT_ID_ERROR_PREFLIGHT,
-                                mCurrentPackage,
+                                currentPackage,
                                 BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                                mBackupManagerMonitorEventSender.putMonitoringExtra(null,
+                                BackupManagerMonitorEventSender.putMonitoringExtra(
+                                        /* extras= */ null,
                                         BackupManagerMonitor.EXTRA_LOG_PREFLIGHT_ERROR,
                                         preflightResult));
                         backupPackageStatus = (int) preflightResult;
@@ -496,9 +497,9 @@
                                     + ": " + totalRead + " of " + quota);
                             mBackupManagerMonitorEventSender.monitorEvent(
                                     BackupManagerMonitor.LOG_EVENT_ID_QUOTA_HIT_PREFLIGHT,
-                                    mCurrentPackage,
+                                    currentPackage,
                                     BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
-                                    null);
+                                    /* extras= */ null);
                             mBackupRunner.sendQuotaExceeded(totalRead, quota);
                         }
                     }
@@ -645,9 +646,9 @@
             Slog.w(TAG, "Exception trying full transport backup", e);
             mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_EXCEPTION_FULL_BACKUP,
-                    mCurrentPackage,
+                    /* pkg= */ null,
                     BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                    mBackupManagerMonitorEventSender.putMonitoringExtra(null,
+                    BackupManagerMonitorEventSender.putMonitoringExtra(/* extras= */ null,
                             BackupManagerMonitor.EXTRA_LOG_EXCEPTION_FULL_BACKUP,
                             Log.getStackTraceString(e)));
 
@@ -966,9 +967,6 @@
             }
         }
 
-
-        // BackupRestoreTask interface: specifically, timeout detection
-
         @Override
         public void execute() { /* intentionally empty */ }
 
@@ -981,7 +979,9 @@
 
             mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_CANCEL,
-                    mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
+                    mTarget,
+                    BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
+                    /* extras= */ null);
             mIsCancelled = true;
             // Cancel tasks spun off by this task.
             mUserBackupManagerService.handleCancel(mEphemeralToken, cancelAll);
diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorEventSender.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorEventSender.java
index c4519b1..33668a6 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorEventSender.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorEventSender.java
@@ -71,6 +71,7 @@
         mMonitor = monitor;
     }
 
+    @Nullable
     public IBackupManagerMonitor getMonitor() {
         return mMonitor;
     }
@@ -87,9 +88,9 @@
      */
     public void monitorEvent(
             int id,
-            PackageInfo pkg,
+            @Nullable PackageInfo pkg,
             int category,
-            Bundle extras) {
+            @Nullable Bundle extras) {
         try {
             Bundle bundle = new Bundle();
             bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID, id);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index b6fe0ad..e46bbe2 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -160,6 +160,7 @@
 import com.android.server.pm.Installer;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.storage.AppFuseBridge;
+import com.android.server.storage.ImmutableVolumeInfo;
 import com.android.server.storage.StorageSessionController;
 import com.android.server.storage.StorageSessionController.ExternalStorageServiceException;
 import com.android.server.storage.WatchedVolumeInfo;
@@ -777,7 +778,7 @@
                     break;
                 }
                 case H_VOLUME_UNMOUNT: {
-                    final WatchedVolumeInfo vol = (WatchedVolumeInfo) msg.obj;
+                    final ImmutableVolumeInfo vol = (ImmutableVolumeInfo) msg.obj;
                     unmount(vol);
                     break;
                 }
@@ -898,8 +899,14 @@
                         for (int i = 0; i < size; i++) {
                             final WatchedVolumeInfo vol = mVolumes.valueAt(i);
                             if (vol.getMountUserId() == userId) {
+                                // Capture the volume before we set mount user id to null,
+                                // so that StorageSessionController remove the session from
+                                // the correct user (old mount user id)
+                                final ImmutableVolumeInfo volToUnmount
+                                        = vol.getClonedImmutableVolumeInfo();
                                 vol.setMountUserId(UserHandle.USER_NULL);
-                                mHandler.obtainMessage(H_VOLUME_UNMOUNT, vol).sendToTarget();
+                                mHandler.obtainMessage(H_VOLUME_UNMOUNT, volToUnmount)
+                                        .sendToTarget();
                             }
                         }
                     }
@@ -1295,7 +1302,12 @@
     }
 
     private void maybeRemountVolumes(int userId) {
-        List<WatchedVolumeInfo> volumesToRemount = new ArrayList<>();
+        // We need to keep 2 lists
+        // 1. List of volumes before we set the mount user Id so that
+        // StorageSessionController is able to remove the session from the correct user (old one)
+        // 2. List of volumes to mount which should have the up to date info
+        List<ImmutableVolumeInfo> volumesToUnmount = new ArrayList<>();
+        List<WatchedVolumeInfo> volumesToMount = new ArrayList<>();
         synchronized (mLock) {
             for (int i = 0; i < mVolumes.size(); i++) {
                 final WatchedVolumeInfo vol = mVolumes.valueAt(i);
@@ -1303,16 +1315,19 @@
                         && vol.getMountUserId() != mCurrentUserId) {
                     // If there's a visible secondary volume mounted,
                     // we need to update the currentUserId and remount
+                    // But capture the volume with the old user id first to use it in unmounting
+                    volumesToUnmount.add(vol.getClonedImmutableVolumeInfo());
                     vol.setMountUserId(mCurrentUserId);
-                    volumesToRemount.add(vol);
+                    volumesToMount.add(vol);
                 }
             }
         }
 
-        for (WatchedVolumeInfo vol : volumesToRemount) {
-            Slog.i(TAG, "Remounting volume for user: " + userId + ". Volume: " + vol);
-            mHandler.obtainMessage(H_VOLUME_UNMOUNT, vol).sendToTarget();
-            mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
+        for (int i = 0; i < volumesToMount.size(); i++) {
+            Slog.i(TAG, "Remounting volume for user: " + userId + ". Volume: "
+                    + volumesToUnmount.get(i));
+            mHandler.obtainMessage(H_VOLUME_UNMOUNT, volumesToUnmount.get(i)).sendToTarget();
+            mHandler.obtainMessage(H_VOLUME_MOUNT, volumesToMount.get(i)).sendToTarget();
         }
     }
 
@@ -2430,10 +2445,10 @@
         super.unmount_enforcePermission();
 
         final WatchedVolumeInfo vol = findVolumeByIdOrThrow(volId);
-        unmount(vol);
+        unmount(vol.getClonedImmutableVolumeInfo());
     }
 
-    private void unmount(WatchedVolumeInfo vol) {
+    private void unmount(ImmutableVolumeInfo vol) {
         try {
             try {
                 if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
@@ -2444,7 +2459,7 @@
             }
             extendWatchdogTimeout("#unmount might be slow");
             mVold.unmount(vol.getId());
-            mStorageSessionController.onVolumeUnmount(vol.getImmutableVolumeInfo());
+            mStorageSessionController.onVolumeUnmount(vol);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
diff --git a/services/core/java/com/android/server/SystemTimeZone.java b/services/core/java/com/android/server/SystemTimeZone.java
index dd07081..c8810f6 100644
--- a/services/core/java/com/android/server/SystemTimeZone.java
+++ b/services/core/java/com/android/server/SystemTimeZone.java
@@ -133,6 +133,7 @@
         boolean timeZoneChanged = false;
         synchronized (SystemTimeZone.class) {
             String currentTimeZoneId = getTimeZoneId();
+            @TimeZoneConfidence int currentConfidence = getTimeZoneConfidence();
             if (currentTimeZoneId == null || !currentTimeZoneId.equals(timeZoneId)) {
                 SystemProperties.set(TIME_ZONE_SYSTEM_PROPERTY, timeZoneId);
                 if (DEBUG) {
@@ -145,6 +146,8 @@
                 String logMsg = "Time zone or confidence set: "
                         + " (new) timeZoneId=" + timeZoneId
                         + ", (new) confidence=" + confidence
+                        + ", (old) timeZoneId=" + currentTimeZoneId
+                        + ", (old) confidence=" + currentConfidence
                         + ", logInfo=" + logInfo;
                 addDebugLogEntry(logMsg);
             }
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 8eda176..296f7cf 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -907,7 +907,7 @@
                     throw new IllegalArgumentException("Unknown mode: " + mode);
             }
 
-            enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser);
+            enforceCurrentUserIfVisibleBackgroundEnabled();
 
             final int user = UserHandle.getCallingUserId();
             final long ident = Binder.clearCallingIdentity();
@@ -970,7 +970,7 @@
                 @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) {
             setAttentionModeThemeOverlay_enforcePermission();
 
-            enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser);
+            enforceCurrentUserIfVisibleBackgroundEnabled();
 
             synchronized (mLock) {
                 if (mAttentionModeThemeOverlay != attentionModeThemeOverlayType) {
@@ -1070,7 +1070,7 @@
                 return false;
             }
 
-            enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser);
+            enforceCurrentUserIfVisibleBackgroundEnabled();
 
             // Store the last requested bedtime night mode state so that we don't need to notify
             // anyone if the user decides to switch to the night mode to bedtime.
@@ -1124,7 +1124,7 @@
                 return;
             }
 
-            enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser);
+            enforceCurrentUserIfVisibleBackgroundEnabled();
 
             final int user = UserHandle.getCallingUserId();
             final long ident = Binder.clearCallingIdentity();
@@ -1155,7 +1155,7 @@
                 return;
             }
 
-            enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser);
+            enforceCurrentUserIfVisibleBackgroundEnabled();
 
             final int user = UserHandle.getCallingUserId();
             final long ident = Binder.clearCallingIdentity();
@@ -1178,7 +1178,7 @@
             assertLegit(callingPackage);
             assertSingleProjectionType(projectionType);
             enforceProjectionTypePermissions(projectionType);
-            enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser);
+            enforceCurrentUserIfVisibleBackgroundEnabled();
 
             synchronized (mLock) {
                 if (mProjectionHolders == null) {
@@ -1224,7 +1224,7 @@
             assertLegit(callingPackage);
             assertSingleProjectionType(projectionType);
             enforceProjectionTypePermissions(projectionType);
-            enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser);
+            enforceCurrentUserIfVisibleBackgroundEnabled();
 
             return releaseProjectionUnchecked(projectionType, callingPackage);
         }
@@ -1266,7 +1266,7 @@
                 return;
             }
 
-            enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser);
+            enforceCurrentUserIfVisibleBackgroundEnabled();
 
             synchronized (mLock) {
                 if (mProjectionListeners == null) {
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
index 7a96195..9937049 100644
--- a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabaseCorruptException;
 import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.os.Environment;
@@ -204,6 +205,11 @@
             return false;
         }
         final String clause = WhiteListReportContract.TIMESTAMP + "< " + untilTimestamp;
-        return db.delete(WhiteListReportContract.TABLE, clause, null) != 0;
+        try {
+            return db.delete(WhiteListReportContract.TABLE, clause, null) != 0;
+        } catch (SQLiteDatabaseCorruptException e) {
+            Slog.e(TAG, "Error deleting records", e);
+            return false;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5a6d7a2..93837b3 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2621,20 +2621,22 @@
      * Valid user is the current user or the system or in the same profile group as the current
      * user. Visible background users are not valid calling users.
      */
-    public static void enforceCurrentUserIfVisibleBackgroundEnabled(@UserIdInt int currentUserId) {
+    public static void enforceCurrentUserIfVisibleBackgroundEnabled() {
         if (!UserManager.isVisibleBackgroundUsersEnabled()) {
             return;
         }
         final int callingUserId = UserHandle.getCallingUserId();
-        if (DBG) {
-            Slog.d(LOG_TAG, "enforceValidCallingUser: callingUserId=" + callingUserId
-                    + " isSystemUser=" + (callingUserId == USER_SYSTEM)
-                    + " currentUserId=" + currentUserId
-                    + " callingPid=" + Binder.getCallingPid()
-                    + " callingUid=" + Binder.getCallingUid());
-        }
         final long ident = Binder.clearCallingIdentity();
         try {
+            final int currentUserId = ActivityManager.getCurrentUser();
+            if (DBG) {
+                Slog.d(LOG_TAG, "enforceCurrentUserIfVisibleBackgroundEnabled:"
+                        + " callingUserId=" + callingUserId
+                        + " isSystemUser=" + (callingUserId == USER_SYSTEM)
+                        + " currentUserId=" + currentUserId
+                        + " callingPid=" + Binder.getCallingPid()
+                        + " callingUid=" + Binder.getCallingUid());
+            }
             if (callingUserId != USER_SYSTEM && callingUserId != currentUserId
                     && !UserManagerService.getInstance()
                     .isSameProfileGroup(callingUserId, currentUserId)) {
diff --git a/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java b/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java
index adf308a..29cc9ea 100644
--- a/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java
+++ b/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java
@@ -143,6 +143,7 @@
     /**
      * Returns the maximum storage size allocated to battery history.
      */
+    @Override
     public int getMaxHistorySize() {
         return mMaxHistorySize;
     }
diff --git a/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java
index 53894a1..f2bbdfc 100644
--- a/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java
@@ -120,16 +120,18 @@
      *                      {@link com.android.internal.os.MonotonicClock}
      * @param currentTime   current time in milliseconds, see {@link System#currentTimeMillis()}
      */
-    void addClockUpdate(long monotonicTime, @CurrentTimeMillisLong long currentTime) {
+    boolean addClockUpdate(long monotonicTime, @CurrentTimeMillisLong long currentTime) {
         ClockUpdate clockUpdate = new ClockUpdate();
         clockUpdate.monotonicTime = monotonicTime;
         clockUpdate.currentTime = currentTime;
         if (mClockUpdates.size() < MAX_CLOCK_UPDATES) {
             mClockUpdates.add(clockUpdate);
+            return true;
         } else {
             Slog.i(TAG, "Too many clock updates. Replacing the previous update with "
                     + DateFormat.format("yyyy-MM-dd-HH-mm-ss", currentTime));
             mClockUpdates.set(mClockUpdates.size() - 1, clockUpdate);
+            return false;
         }
     }
 
diff --git a/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java
index 8461a54..b9862aa 100644
--- a/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java
@@ -98,14 +98,18 @@
 
                     if (!startedSession) {
                         mStats.start(item.time);
-                        mStats.addClockUpdate(item.time, item.currentTime);
+                        if (!mStats.addClockUpdate(item.time, item.currentTime)) {
+                            break;
+                        }
                         if (baseTime == UNINITIALIZED) {
                             baseTime = item.time;
                         }
                         startedSession = true;
                     } else if (item.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME
                                || item.cmd == BatteryStats.HistoryItem.CMD_RESET) {
-                        mStats.addClockUpdate(item.time, item.currentTime);
+                        if (!mStats.addClockUpdate(item.time, item.currentTime)) {
+                            break;
+                        }
                     }
 
                     lastTime = item.time;
@@ -164,7 +168,9 @@
                                 consumer.accept(mStats);
                             }
                             mStats.reset();
-                            mStats.addClockUpdate(item.time, item.currentTime);
+                            if (!mStats.addClockUpdate(item.time, item.currentTime)) {
+                                break;
+                            }
                             baseTime = lastTime = item.time;
                         }
                         mStats.addPowerStats(item.powerStats, item.time);
diff --git a/services/core/java/com/android/server/storage/WatchedVolumeInfo.java b/services/core/java/com/android/server/storage/WatchedVolumeInfo.java
index 94e52cd..d4b20fb 100644
--- a/services/core/java/com/android/server/storage/WatchedVolumeInfo.java
+++ b/services/core/java/com/android/server/storage/WatchedVolumeInfo.java
@@ -68,6 +68,10 @@
         return ImmutableVolumeInfo.fromVolumeInfo(mVolumeInfo);
     }
 
+    public ImmutableVolumeInfo getClonedImmutableVolumeInfo() {
+        return ImmutableVolumeInfo.fromVolumeInfo(mVolumeInfo.clone());
+    }
+
     public StorageVolume buildStorageVolume(Context context, int userId, boolean reportUnmounted) {
         return mVolumeInfo.buildStorageVolume(context, userId, reportUnmounted);
     }
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
index bda3d44..621a128 100644
--- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -51,6 +51,7 @@
 final class VendorVibrationSession extends IVibrationSession.Stub
         implements VibrationSession, CancellationSignal.OnCancelListener, IBinder.DeathRecipient {
     private static final String TAG = "VendorVibrationSession";
+    private static final boolean DEBUG = false;
 
     /** Calls into VibratorManager functionality needed for playing an {@link ExternalVibration}. */
     interface VibratorManagerHooks {
@@ -73,8 +74,8 @@
     private final ICancellationSignal mCancellationSignal = CancellationSignal.createTransport();
     private final int[] mVibratorIds;
     private final long mCreateUptime;
-    private final long mCreateTime; // for debugging
-    private final IVibrationSessionCallback mCallback;
+    private final long mCreateTime;
+    private final VendorCallbackWrapper mCallback;
     private final CallerInfo mCallerInfo;
     private final VibratorManagerHooks mManagerHooks;
     private final DeviceAdapter mDeviceAdapter;
@@ -88,11 +89,11 @@
     @GuardedBy("mLock")
     private boolean mEndedByVendor;
     @GuardedBy("mLock")
-    private long mStartTime; // for debugging
+    private long mStartTime;
     @GuardedBy("mLock")
     private long mEndUptime;
     @GuardedBy("mLock")
-    private long mEndTime; // for debugging
+    private long mEndTime;
     @GuardedBy("mLock")
     private VibrationStepConductor mConductor;
 
@@ -103,7 +104,7 @@
         mCreateTime = System.currentTimeMillis();
         mVibratorIds = deviceAdapter.getAvailableVibratorIds();
         mHandler = handler;
-        mCallback = callback;
+        mCallback = new VendorCallbackWrapper(callback, handler);
         mCallerInfo = callerInfo;
         mManagerHooks = managerHooks;
         mDeviceAdapter = deviceAdapter;
@@ -119,7 +120,9 @@
 
     @Override
     public void finishSession() {
-        Slog.d(TAG, "Session finish requested, ending vibration session...");
+        if (DEBUG) {
+            Slog.d(TAG, "Session finish requested, ending vibration session...");
+        }
         // Do not abort session in HAL, wait for ongoing vibration requests to complete.
         // This might take a while to end the session, but it can be aborted by cancelSession.
         requestEndSession(Status.FINISHED, /* shouldAbort= */ false, /* isVendorRequest= */ true);
@@ -127,7 +130,9 @@
 
     @Override
     public void cancelSession() {
-        Slog.d(TAG, "Session cancel requested, aborting vibration session...");
+        if (DEBUG) {
+            Slog.d(TAG, "Session cancel requested, aborting vibration session...");
+        }
         // Always abort session in HAL while cancelling it.
         // This might be triggered after finishSession was already called.
         requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
@@ -156,7 +161,7 @@
 
     @Override
     public IBinder getCallerToken() {
-        return mCallback.asBinder();
+        return mCallback.getBinderToken();
     }
 
     @Override
@@ -176,36 +181,30 @@
 
     @Override
     public void onCancel() {
-        Slog.d(TAG, "Session cancellation signal received, aborting vibration session...");
+        if (DEBUG) {
+            Slog.d(TAG, "Session cancellation signal received, aborting vibration session...");
+        }
         requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
                 /* isVendorRequest= */ true);
     }
 
     @Override
     public void binderDied() {
-        Slog.d(TAG, "Session binder died, aborting vibration session...");
+        if (DEBUG) {
+            Slog.d(TAG, "Session binder died, aborting vibration session...");
+        }
         requestEndSession(Status.CANCELLED_BINDER_DIED, /* shouldAbort= */ true,
                 /* isVendorRequest= */ false);
     }
 
     @Override
     public boolean linkToDeath() {
-        try {
-            mCallback.asBinder().linkToDeath(this, 0);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Error linking session to token death", e);
-            return false;
-        }
-        return true;
+        return mCallback.linkToDeath(this);
     }
 
     @Override
     public void unlinkToDeath() {
-        try {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        } catch (NoSuchElementException e) {
-            Slog.wtf(TAG, "Failed to unlink session to token death", e);
-        }
+        mCallback.unlinkToDeath(this);
     }
 
     @Override
@@ -219,26 +218,37 @@
 
     @Override
     public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) {
-        Slog.d(TAG, "Vibration callback received for vibration " + vibrationId + " step " + stepId
-                + " on vibrator " + vibratorId + ", ignoring...");
+        if (DEBUG) {
+            Slog.d(TAG, "Vibration callback received for vibration " + vibrationId
+                    + " step " + stepId + " on vibrator " + vibratorId + ", ignoring...");
+        }
     }
 
     @Override
     public void notifySyncedVibratorsCallback(long vibrationId) {
-        Slog.d(TAG, "Synced vibration callback received for vibration " + vibrationId
-                + ", ignoring...");
+        if (DEBUG) {
+            Slog.d(TAG, "Synced vibration callback received for vibration " + vibrationId
+                    + ", ignoring...");
+        }
     }
 
     @Override
     public void notifySessionCallback() {
-        Slog.d(TAG, "Session callback received, ending vibration session...");
+        if (DEBUG) {
+            Slog.d(TAG, "Session callback received, ending vibration session...");
+        }
         synchronized (mLock) {
             // If end was not requested then the HAL has cancelled the session.
-            maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON,
+            notifyEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON,
                     /* isVendorRequest= */ false);
             maybeSetStatusToRequestedLocked();
             clearVibrationConductor();
-            mHandler.post(() -> mManagerHooks.onSessionReleased(mSessionId));
+            final Status endStatus = mStatus;
+            mHandler.post(() -> {
+                mManagerHooks.onSessionReleased(mSessionId);
+                // Only trigger client callback after session is released in the manager.
+                mCallback.notifyFinished(endStatus);
+            });
         }
     }
 
@@ -271,7 +281,7 @@
 
     public boolean isEnded() {
         synchronized (mLock) {
-            return mStatus != Status.RUNNING;
+            return mEndTime > 0;
         }
     }
 
@@ -297,19 +307,17 @@
                 // Session already ended, skip start callbacks.
                 isAlreadyEnded = true;
             } else {
+                if (DEBUG) {
+                    Slog.d(TAG, "Session started at the HAL");
+                }
                 mStartTime = System.currentTimeMillis();
-                // Run client callback in separate thread.
-                mHandler.post(() -> {
-                    try {
-                        mCallback.onStarted(this);
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "Error notifying vendor session started", e);
-                    }
-                });
+                mCallback.notifyStarted(this);
             }
         }
         if (isAlreadyEnded) {
-            Slog.d(TAG, "Session already ended after starting the HAL, aborting...");
+            if (DEBUG) {
+                Slog.d(TAG, "Session already ended after starting the HAL, aborting...");
+            }
             mHandler.post(() -> mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true));
         }
     }
@@ -337,8 +345,10 @@
     public boolean maybeSetVibrationConductor(VibrationStepConductor conductor) {
         synchronized (mLock) {
             if (mConductor != null) {
-                Slog.d(TAG, "Session still dispatching previous vibration, new vibration "
-                        + conductor.getVibration().id + " ignored");
+                if (DEBUG) {
+                    Slog.d(TAG, "Session still dispatching previous vibration, new vibration "
+                            + conductor.getVibration().id + " ignored");
+                }
                 return false;
             }
             mConductor = conductor;
@@ -347,53 +357,45 @@
     }
 
     private void requestEndSession(Status status, boolean shouldAbort, boolean isVendorRequest) {
-        Slog.d(TAG, "Session end request received with status " + status);
-        boolean shouldTriggerSessionHook = false;
+        if (DEBUG) {
+            Slog.d(TAG, "Session end request received with status " + status);
+        }
         synchronized (mLock) {
-            maybeSetEndRequestLocked(status, isVendorRequest);
+            notifyEndRequestLocked(status, isVendorRequest);
             if (!isEnded() && isStarted()) {
                 // Trigger session hook even if it was already triggered, in case a second request
                 // is aborting the ongoing/ending session. This might cause it to end right away.
                 // Wait for HAL callback before setting the end status.
-                shouldTriggerSessionHook = true;
+                if (DEBUG) {
+                    Slog.d(TAG, "Requesting HAL session end with abort=" + shouldAbort);
+                }
+                mHandler.post(() ->  mManagerHooks.endSession(mSessionId, shouldAbort));
             } else {
-                // Session not active in the HAL, set end status right away.
+                // Session not active in the HAL, try to set end status right away.
                 maybeSetStatusToRequestedLocked();
+                // Use status used to end this session, which might be different from requested.
+                mCallback.notifyFinished(mStatus);
             }
         }
-        if (shouldTriggerSessionHook) {
-            Slog.d(TAG, "Requesting HAL session end with abort=" + shouldAbort);
-            mHandler.post(() ->  mManagerHooks.endSession(mSessionId, shouldAbort));
-        }
     }
 
     @GuardedBy("mLock")
-    private void maybeSetEndRequestLocked(Status status, boolean isVendorRequest) {
+    private void notifyEndRequestLocked(Status status, boolean isVendorRequest) {
         if (mEndStatusRequest != null) {
-            // End already requested, keep first requested status and time.
+            // End already requested, keep first requested status.
             return;
         }
-        Slog.d(TAG, "Session end request accepted for status " + status);
+        if (DEBUG) {
+            Slog.d(TAG, "Session end request accepted for status " + status);
+        }
         mEndStatusRequest = status;
         mEndedByVendor = isVendorRequest;
-        mEndTime = System.currentTimeMillis();
-        mEndUptime = SystemClock.uptimeMillis();
+        mCallback.notifyFinishing();
         if (mConductor != null) {
             // Vibration is being dispatched when session end was requested, cancel it.
             mConductor.notifyCancelled(new Vibration.EndInfo(status),
                     /* immediate= */ status != Status.FINISHED);
         }
-        if (isStarted()) {
-            // Only trigger "finishing" callback if session started.
-            // Run client callback in separate thread.
-            mHandler.post(() -> {
-                try {
-                    mCallback.onFinishing();
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Error notifying vendor session is finishing", e);
-                }
-            });
-        }
     }
 
     @GuardedBy("mLock")
@@ -406,40 +408,123 @@
             // No end status was requested, nothing to set.
             return;
         }
-        Slog.d(TAG, "Session end request applied for status " + mEndStatusRequest);
+        if (DEBUG) {
+            Slog.d(TAG, "Session end request applied for status " + mEndStatusRequest);
+        }
         mStatus = mEndStatusRequest;
-        // Run client callback in separate thread.
-        final Status endStatus = mStatus;
-        mHandler.post(() -> {
-            try {
-                mCallback.onFinished(toSessionStatus(endStatus));
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Error notifying vendor session finished", e);
-            }
-        });
+        mEndTime = System.currentTimeMillis();
+        mEndUptime = SystemClock.uptimeMillis();
     }
 
-    @android.os.vibrator.VendorVibrationSession.Status
-    private static int toSessionStatus(Status status) {
-        // Exhaustive switch to cover all possible internal status.
-        return switch (status) {
-            case FINISHED
-                    -> android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS;
-            case IGNORED_UNSUPPORTED
-                    -> STATUS_UNSUPPORTED;
-            case CANCELLED_BINDER_DIED, CANCELLED_BY_APP_OPS, CANCELLED_BY_USER,
-                 CANCELLED_SUPERSEDED, CANCELLED_BY_FOREGROUND_USER, CANCELLED_BY_SCREEN_OFF,
-                 CANCELLED_BY_SETTINGS_UPDATE, CANCELLED_BY_UNKNOWN_REASON
-                    -> android.os.vibrator.VendorVibrationSession.STATUS_CANCELED;
-            case IGNORED_APP_OPS, IGNORED_BACKGROUND, IGNORED_FOR_EXTERNAL, IGNORED_FOR_ONGOING,
-                 IGNORED_FOR_POWER, IGNORED_FOR_SETTINGS, IGNORED_FOR_HIGHER_IMPORTANCE,
-                 IGNORED_FOR_RINGER_MODE, IGNORED_FROM_VIRTUAL_DEVICE, IGNORED_SUPERSEDED,
-                 IGNORED_MISSING_PERMISSION, IGNORED_ON_WIRELESS_CHARGER
-                    -> android.os.vibrator.VendorVibrationSession.STATUS_IGNORED;
-            case UNKNOWN, IGNORED_ERROR_APP_OPS, IGNORED_ERROR_CANCELLING, IGNORED_ERROR_SCHEDULING,
-                 IGNORED_ERROR_TOKEN, FORWARDED_TO_INPUT_DEVICES, FINISHED_UNEXPECTED, RUNNING
-                    -> android.os.vibrator.VendorVibrationSession.STATUS_UNKNOWN_ERROR;
-        };
+    /**
+     * Wrapper class to handle client callbacks asynchronously.
+     *
+     * <p>This class is also responsible for link/unlink to the client process binder death, and for
+     * making sure the callbacks are only triggered once. The conversion between session status and
+     * the API status code is also defined here.
+     */
+    private static final class VendorCallbackWrapper {
+        private final IVibrationSessionCallback mCallback;
+        private final Handler mHandler;
+
+        private boolean mIsStarted;
+        private boolean mIsFinishing;
+        private boolean mIsFinished;
+
+        VendorCallbackWrapper(@NonNull IVibrationSessionCallback callback,
+                @NonNull Handler handler) {
+            mCallback = callback;
+            mHandler = handler;
+        }
+
+        synchronized IBinder getBinderToken() {
+            return mCallback.asBinder();
+        }
+
+        synchronized boolean linkToDeath(DeathRecipient recipient) {
+            try {
+                mCallback.asBinder().linkToDeath(recipient, 0);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error linking session to token death", e);
+                return false;
+            }
+            return true;
+        }
+
+        synchronized void unlinkToDeath(DeathRecipient recipient) {
+            try {
+                mCallback.asBinder().unlinkToDeath(recipient, 0);
+            } catch (NoSuchElementException e) {
+                Slog.wtf(TAG, "Failed to unlink session to token death", e);
+            }
+        }
+
+        synchronized void notifyStarted(IVibrationSession session) {
+            if (mIsStarted) {
+                return;
+            }
+            mIsStarted = true;
+            mHandler.post(() -> {
+                try {
+                    mCallback.onStarted(session);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error notifying vendor session started", e);
+                }
+            });
+        }
+
+        synchronized void notifyFinishing() {
+            if (!mIsStarted || mIsFinishing || mIsFinished) {
+                // Ignore if never started or if already finishing or finished.
+                return;
+            }
+            mIsFinishing = true;
+            mHandler.post(() -> {
+                try {
+                    mCallback.onFinishing();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error notifying vendor session is finishing", e);
+                }
+            });
+        }
+
+        synchronized void notifyFinished(Status status) {
+            if (mIsFinished) {
+                return;
+            }
+            mIsFinished = true;
+            mHandler.post(() -> {
+                try {
+                    mCallback.onFinished(toSessionStatus(status));
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error notifying vendor session finished", e);
+                }
+            });
+        }
+
+        @android.os.vibrator.VendorVibrationSession.Status
+        private static int toSessionStatus(Status status) {
+            // Exhaustive switch to cover all possible internal status.
+            return switch (status) {
+                case FINISHED
+                        -> android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS;
+                case IGNORED_UNSUPPORTED
+                        -> STATUS_UNSUPPORTED;
+                case CANCELLED_BINDER_DIED, CANCELLED_BY_APP_OPS, CANCELLED_BY_USER,
+                     CANCELLED_SUPERSEDED, CANCELLED_BY_FOREGROUND_USER, CANCELLED_BY_SCREEN_OFF,
+                     CANCELLED_BY_SETTINGS_UPDATE, CANCELLED_BY_UNKNOWN_REASON
+                        -> android.os.vibrator.VendorVibrationSession.STATUS_CANCELED;
+                case IGNORED_APP_OPS, IGNORED_BACKGROUND, IGNORED_FOR_EXTERNAL, IGNORED_FOR_ONGOING,
+                     IGNORED_FOR_POWER, IGNORED_FOR_SETTINGS, IGNORED_FOR_HIGHER_IMPORTANCE,
+                     IGNORED_FOR_RINGER_MODE, IGNORED_FROM_VIRTUAL_DEVICE, IGNORED_SUPERSEDED,
+                     IGNORED_MISSING_PERMISSION, IGNORED_ON_WIRELESS_CHARGER
+                        -> android.os.vibrator.VendorVibrationSession.STATUS_IGNORED;
+                case UNKNOWN, IGNORED_ERROR_APP_OPS, IGNORED_ERROR_CANCELLING,
+                     IGNORED_ERROR_SCHEDULING, IGNORED_ERROR_TOKEN, FORWARDED_TO_INPUT_DEVICES,
+                     FINISHED_UNEXPECTED, RUNNING
+                        -> android.os.vibrator.VendorVibrationSession.STATUS_UNKNOWN_ERROR;
+            };
+        }
     }
 
     /**
@@ -499,7 +584,7 @@
         @Override
         public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
             if (mStartTime > 0) {
-                // Only log sessions that have started.
+                // Only log sessions that have started in the HAL.
                 statsLogger.logVibrationVendorSessionStarted(mCallerInfo.uid);
                 statsLogger.logVibrationVendorSessionVibrations(mCallerInfo.uid,
                         mVibrations.size());
diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java
index c2255d8..dc42b32 100644
--- a/services/core/java/com/android/server/wm/DesktopModeHelper.java
+++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java
@@ -79,7 +79,7 @@
     }
 
     @VisibleForTesting
-    static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
+    public static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
         if (!shouldEnforceDeviceRestrictions()) {
             return true;
         }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e190a04..50f12c3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6938,6 +6938,8 @@
         /** The actual requested visible inset types for this display */
         private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
 
+        private @InsetsType int mAnimatingTypes = 0;
+
         /** The component name of the top focused window on this display */
         private ComponentName mTopFocusedComponentName = null;
 
@@ -7075,6 +7077,18 @@
             }
             return 0;
         }
+
+        @Override
+        public @InsetsType int getAnimatingTypes() {
+            return mAnimatingTypes;
+        }
+
+        @Override
+        public void setAnimatingTypes(@InsetsType int animatingTypes) {
+            if (mAnimatingTypes != animatingTypes) {
+                mAnimatingTypes = animatingTypes;
+            }
+        }
     }
 
     MagnificationSpec getMagnificationSpec() {
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index cee4967..6462a37 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -97,6 +97,20 @@
             @NonNull ImeTracker.Token statsToken) {
     }
 
+    /**
+     * @return {@link WindowInsets.Type.InsetsType}s which are currently animating (showing or
+     * hiding).
+     */
+    default @InsetsType int getAnimatingTypes() {
+        return 0;
+    }
+
+    /**
+     * @param animatingTypes the {@link InsetsType}s, that are currently animating
+     */
+    default void setAnimatingTypes(@InsetsType int animatingTypes) {
+    }
+
     /** Returns {@code target.getWindow()}, or null if {@code target} is {@code null}. */
     static WindowState asWindowOrNull(InsetsControlTarget target) {
         return target != null ? target.getWindow() : null;
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 009d482..2872214 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -790,8 +790,6 @@
         private final Handler mHandler;
         private final String mName;
 
-        private boolean mInsetsAnimationRunning;
-
         Host(Handler handler, String name) {
             mHandler = handler;
             mName = name;
@@ -901,10 +899,5 @@
         public IBinder getWindowToken() {
             return null;
         }
-
-        @Override
-        public void notifyAnimationRunningStateChanged(boolean running) {
-            mInsetsAnimationRunning = running;
-        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 8d198b2..3ed16db 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -737,6 +737,17 @@
         }
     }
 
+    @Override
+    public void updateAnimatingTypes(IWindow window, @InsetsType int animatingTypes) {
+        synchronized (mService.mGlobalLock) {
+            final WindowState win = mService.windowForClientLocked(this, window,
+                    false /* throwOnError */);
+            if (win != null) {
+                win.setAnimatingTypes(animatingTypes);
+            }
+        }
+    }
+
     void onWindowAdded(WindowState w) {
         if (mPackageName == null) {
             mPackageName = mProcess.mInfo.packageName;
@@ -1015,15 +1026,4 @@
             }
         }
     }
-
-    @Override
-    public void notifyInsetsAnimationRunningStateChanged(IWindow window, boolean running) {
-        synchronized (mService.mGlobalLock) {
-            final WindowState win = mService.windowForClientLocked(this, window,
-                    false /* throwOnError */);
-            if (win != null) {
-                win.notifyInsetsAnimationRunningStateChanged(running);
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2e699ca..28f2825 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4773,6 +4773,26 @@
         }
     }
 
+    @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+    @Override
+    public void updateDisplayWindowAnimatingTypes(int displayId, @InsetsType int animatingTypes) {
+        updateDisplayWindowAnimatingTypes_enforcePermission();
+        if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                synchronized (mGlobalLock) {
+                    final DisplayContent dc = mRoot.getDisplayContent(displayId);
+                    if (dc == null || dc.mRemoteInsetsControlTarget == null) {
+                        return;
+                    }
+                    dc.mRemoteInsetsControlTarget.setAnimatingTypes(animatingTypes);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
     @Override
     public int watchRotation(IRotationWatcher watcher, int displayId) {
         final DisplayContent displayContent;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ce87ca5..3b7d312 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -736,6 +736,8 @@
 
     private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
 
+    private @InsetsType int mAnimatingTypes = 0;
+
     /**
      * Freeze the insets state in some cases that not necessarily keeps up-to-date to the client.
      * (e.g app exiting transition)
@@ -842,6 +844,27 @@
                 mRequestedVisibleTypes & ~mask | requestedVisibleTypes & mask);
     }
 
+    @Override
+    public @InsetsType int getAnimatingTypes() {
+        return mAnimatingTypes;
+    }
+
+    @Override
+    public void setAnimatingTypes(@InsetsType int animatingTypes) {
+        if (mAnimatingTypes != animatingTypes) {
+            if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+                Trace.instant(TRACE_TAG_WINDOW_MANAGER,
+                        TextUtils.formatSimple("%s: setAnimatingTypes(%s)",
+                                getName(),
+                                animatingTypes));
+            }
+            mInsetsAnimationRunning = animatingTypes != 0;
+            mWmService.scheduleAnimationLocked();
+
+            mAnimatingTypes = animatingTypes;
+        }
+    }
+
     /**
      * Set a freeze state for the window to ignore dispatching its insets state to the client.
      *
@@ -6078,17 +6101,6 @@
         mWmService.scheduleAnimationLocked();
     }
 
-    void notifyInsetsAnimationRunningStateChanged(boolean running) {
-        if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
-            Trace.instant(TRACE_TAG_WINDOW_MANAGER,
-                    TextUtils.formatSimple("%s: notifyInsetsAnimationRunningStateChanged(%s)",
-                    getName(),
-                    Boolean.toString(running)));
-        }
-        mInsetsAnimationRunning = running;
-        mWmService.scheduleAnimationLocked();
-    }
-
     boolean isInsetsAnimationRunning() {
         return mInsetsAnimationRunning;
     }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e158310..860b6fb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1814,7 +1814,7 @@
                 t.traceEnd();
             }
 
-            if (!isWatch && !isTv && !isAutomotive
+            if (!isWatch && !isTv && !isAutomotive && !isDesktop
                     && android.security.Flags.aapmApi()) {
                 t.traceBegin("StartAdvancedProtectionService");
                 mSystemServiceManager.startService(AdvancedProtectionService.Lifecycle.class);
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index bada337..6b8ef88 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -64,7 +64,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
 import android.graphics.Color;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
@@ -95,6 +94,7 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
+import com.android.server.wm.DesktopModeHelper;
 import com.android.server.wm.WindowManagerInternal;
 
 import org.hamcrest.CoreMatchers;
@@ -155,8 +155,6 @@
 
     private IPackageManager mIpm = AppGlobals.getPackageManager();
 
-    private Resources mResources = sContext.getResources();
-
     @Mock
     private DisplayManager mDisplayManager;
 
@@ -178,6 +176,7 @@
                 .spyStatic(WallpaperUtils.class)
                 .spyStatic(LocalServices.class)
                 .spyStatic(WallpaperManager.class)
+                .spyStatic(DesktopModeHelper.class)
                 .startMocking();
 
         sWindowManagerInternal = mock(WindowManagerInternal.class);
@@ -246,6 +245,8 @@
             int userId = (invocation.getArgument(0));
             return getWallpaperTestDir(userId);
         }).when(() -> WallpaperUtils.getWallpaperDir(anyInt()));
+        ExtendedMockito.doAnswer(invocation -> true).when(
+                () -> DesktopModeHelper.isDeviceEligibleForDesktopMode(any()));
 
         sContext.addMockSystemService(DisplayManager.class, mDisplayManager);
 
@@ -257,10 +258,6 @@
         doReturn(displays).when(mDisplayManager).getDisplays();
 
         spyOn(mIpm);
-        spyOn(mResources);
-        doReturn(true).when(mResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
-        doReturn(true).when(mResources).getBoolean(
-                eq(R.bool.config_canInternalDisplayHostDesktops));
         mService = new TestWallpaperManagerService(sContext);
         spyOn(mService);
         mService.systemReady();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 45436e4..d3f3269 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -33,6 +33,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.Display.Mode;
 import android.view.Surface;
+import android.view.WindowInsets;
 import android.view.WindowManager.LayoutParams;
 
 import androidx.test.filters.SmallTest;
@@ -283,7 +284,7 @@
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
 
-        overrideWindow.notifyInsetsAnimationRunningStateChanged(true);
+        overrideWindow.setAnimatingTypes(WindowInsets.Type.statusBars());
         assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
         assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
         assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
@@ -303,7 +304,7 @@
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
 
-        overrideWindow.notifyInsetsAnimationRunningStateChanged(true);
+        overrideWindow.setAnimatingTypes(WindowInsets.Type.statusBars());
         assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
         assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
         assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 6e0304b..fbba999 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -10579,9 +10579,19 @@
     public boolean hasCarrierPrivileges(int subId) {
         try {
             ITelephony telephony = getITelephony();
-            if (telephony != null) {
-                return telephony.getCarrierPrivilegeStatus(subId)
-                        == CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+            if (telephony == null) {
+                Rlog.e(TAG, "hasCarrierPrivileges: no Telephony service");
+                return false;
+            }
+            int status = telephony.getCarrierPrivilegeStatus(subId);
+            switch (status) {
+                case CARRIER_PRIVILEGE_STATUS_HAS_ACCESS:
+                    return true;
+                case CARRIER_PRIVILEGE_STATUS_NO_ACCESS:
+                    return false;
+                default:
+                    Rlog.e(TAG, "hasCarrierPrivileges: " + status);
+                    return false;
             }
         } catch (RemoteException ex) {
             Rlog.e(TAG, "hasCarrierPrivileges RemoteException", ex);