Merge "Prevent windows from dimming if host is going away" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index fbc8eef..216bbab 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -34264,6 +34264,7 @@
     method public boolean hasFileDescriptors();
     method public boolean hasFileDescriptors(int, int);
     method public byte[] marshall();
+    method @FlaggedApi("android.os.parcel_marshall_bytebuffer") public void marshall(@NonNull java.nio.ByteBuffer);
     method @NonNull public static android.os.Parcel obtain();
     method @NonNull public static android.os.Parcel obtain(@NonNull android.os.IBinder);
     method @Deprecated @Nullable public Object[] readArray(@Nullable ClassLoader);
@@ -34333,6 +34334,7 @@
     method public void setDataSize(int);
     method public void setPropagateAllowBlocking();
     method public void unmarshall(@NonNull byte[], int, int);
+    method @FlaggedApi("android.os.parcel_marshall_bytebuffer") public void unmarshall(@NonNull java.nio.ByteBuffer);
     method public void writeArray(@Nullable Object[]);
     method public void writeBinderArray(@Nullable android.os.IBinder[]);
     method public void writeBinderList(@Nullable java.util.List<android.os.IBinder>);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 8af5b1b..19fecb9 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -101,14 +101,20 @@
      */
     public static final String REPORT_KEY_STREAMRESULT = "stream";
 
-    static final String TAG = "Instrumentation";
+    /**
+     * @hide
+     */
+    public static final String TAG = "Instrumentation";
 
     private static final long CONNECT_TIMEOUT_MILLIS = 60_000;
 
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
-    // If set, will print the stack trace for activity starts within the process
-    static final boolean DEBUG_START_ACTIVITY = Build.IS_DEBUGGABLE &&
+    /**
+     * If set, will print the stack trace for activity starts within the process
+     * @hide
+     */
+    public static final boolean DEBUG_START_ACTIVITY = Build.IS_DEBUGGABLE &&
             SystemProperties.getBoolean("persist.wm.debug.start_activity", false);
     static final boolean DEBUG_FINISH_ACTIVITY = Build.IS_DEBUGGABLE &&
             SystemProperties.getBoolean("persist.wm.debug.finish_activity", false);
diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java
index afe915e..dd87d28 100644
--- a/core/java/android/app/PictureInPictureParams.java
+++ b/core/java/android/app/PictureInPictureParams.java
@@ -594,7 +594,7 @@
      * @see PictureInPictureParams.Builder#setSeamlessResizeEnabled(boolean)
      */
     public boolean isSeamlessResizeEnabled() {
-        return mSeamlessResizeEnabled == null ? true : mSeamlessResizeEnabled;
+        return mSeamlessResizeEnabled == null ? false : mSeamlessResizeEnabled;
     }
 
     /**
diff --git a/core/java/android/app/admin/StringSetIntersection.java b/core/java/android/app/admin/StringSetIntersection.java
new file mode 100644
index 0000000..5f2031e
--- /dev/null
+++ b/core/java/android/app/admin/StringSetIntersection.java
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+package android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Set;
+
+/**
+ * Class to identify a intersection resolution mechanism for {@code Set<String>} policies, it's
+ * used to resolve the enforced policy when being set by multiple admins (see {@link
+ * PolicyState#getResolutionMechanism()}).
+ *
+ * @hide
+ */
+public final class StringSetIntersection extends ResolutionMechanism<Set<String>> {
+
+    /**
+     * Intersection resolution for policies represented {@code Set<String>} which resolves as the
+     * intersection of all sets.
+     */
+    @NonNull
+    public static final StringSetIntersection STRING_SET_INTERSECTION = new StringSetIntersection();
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        return o != null && getClass() == o.getClass();
+    }
+
+    @Override
+    public int hashCode() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "StringSetIntersection {}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {}
+
+    @NonNull
+    public static final Parcelable.Creator<StringSetIntersection> CREATOR =
+            new Parcelable.Creator<StringSetIntersection>() {
+                @Override
+                public StringSetIntersection createFromParcel(Parcel source) {
+                    return new StringSetIntersection();
+                }
+
+                @Override
+                public StringSetIntersection[] newArray(int size) {
+                    return new StringSetIntersection[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 572bffe..b87ef70 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -412,3 +412,13 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "use_policy_intersection_for_permitted_input_methods"
+  namespace: "enterprise"
+  description: "When deciding on permitted input methods, use policy intersection instead of last recorded policy."
+  bug: "340914586"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 6cb49b3..4a99285 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -20,6 +20,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -27,6 +28,7 @@
 import android.annotation.TestApi;
 import android.app.AppOpsManager;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Flags;
 import android.ravenwood.annotation.RavenwoodClassLoadHook;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.ravenwood.annotation.RavenwoodReplace;
@@ -837,9 +839,8 @@
      * @param buffer The ByteBuffer to write the data to.
      * @throws ReadOnlyBufferException if the buffer is read-only.
      * @throws BufferOverflowException if the buffer is too small.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_PARCEL_MARSHALL_BYTEBUFFER)
     public final void marshall(@NonNull ByteBuffer buffer) {
         if (buffer == null) {
             throw new NullPointerException();
@@ -875,9 +876,8 @@
      * Fills the raw bytes of this Parcel with data from the supplied buffer.
      *
      * @param buffer will read buffer.remaining() bytes from the buffer.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_PARCEL_MARSHALL_BYTEBUFFER)
     public final void unmarshall(@NonNull ByteBuffer buffer) {
         if (buffer == null) {
             throw new NullPointerException();
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 86acb2b..0150d17 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -354,6 +354,15 @@
 
 flag {
      namespace: "system_performance"
+     name: "parcel_marshall_bytebuffer"
+     is_exported: true
+     description: "Parcel marshal/unmarshall APIs that use ByteBuffer."
+     is_fixed_read_only: true
+     bug: "401362825"
+}
+
+flag {
+     namespace: "system_performance"
      name: "perfetto_sdk_tracing"
      description: "Tracing using Perfetto SDK."
      bug: "303199244"
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 2ed9c3a..8f7f941 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -16,6 +16,7 @@
 
 package android.window;
 
+import static android.app.Instrumentation.DEBUG_START_ACTIVITY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
@@ -32,6 +33,7 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
+import android.app.Instrumentation;
 import android.app.PendingIntent;
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
@@ -45,6 +47,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
 import android.view.SurfaceControl;
@@ -642,6 +645,10 @@
      */
     @NonNull
     public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) {
+        if (DEBUG_START_ACTIVITY) {
+            Log.d(Instrumentation.TAG, "WCT.startTask: taskId=" + taskId
+                    + " options=" + options, new Throwable());
+        }
         mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options));
         return this;
     }
@@ -655,11 +662,15 @@
      */
     @NonNull
     public WindowContainerTransaction sendPendingIntent(@Nullable PendingIntent sender,
-            @Nullable Intent intent, @Nullable Bundle options) {
+            @Nullable Intent fillInIntent, @Nullable Bundle options) {
+        if (DEBUG_START_ACTIVITY) {
+            Log.d(Instrumentation.TAG, "WCT.sendPendingIntent: sender=" + sender.getIntent()
+                    + " fillInIntent=" + fillInIntent + " options=" + options, new Throwable());
+        }
         mHierarchyOps.add(new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT)
                 .setLaunchOptions(options)
                 .setPendingIntent(sender)
-                .setActivityIntent(intent)
+                .setActivityIntent(fillInIntent)
                 .build());
         return this;
     }
@@ -674,6 +685,10 @@
     @NonNull
     public WindowContainerTransaction startShortcut(@NonNull String callingPackage,
             @NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options) {
+        if (DEBUG_START_ACTIVITY) {
+            Log.d(Instrumentation.TAG, "WCT.startShortcut: shortcutInfo=" + shortcutInfo
+                    + " options=" + options, new Throwable());
+        }
         mHierarchyOps.add(HierarchyOp.createForStartShortcut(
                 callingPackage, shortcutInfo, options));
         return this;
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index bb05910..3e652010 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -29,8 +29,6 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import java.nio.BufferOverflowException;
-import java.nio.ByteBuffer;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -418,63 +416,4 @@
         int binderEndPos = pA.dataPosition();
         assertTrue(pA.hasBinders(binderStartPos, binderEndPos - binderStartPos));
     }
-
-    private static final byte[] TEST_DATA = new byte[] {4, 8, 15, 16, 23, 42};
-
-    // Allow for some Parcel overhead
-    private static final int TEST_DATA_LENGTH = TEST_DATA.length + 100;
-
-    @Test
-    public void testMarshall_ByteBuffer_wrapped() {
-        ByteBuffer bb = ByteBuffer.allocate(TEST_DATA_LENGTH);
-        testMarshall_ByteBuffer(bb);
-    }
-
-    @Test
-    public void testMarshall_DirectByteBuffer() {
-        ByteBuffer bb = ByteBuffer.allocateDirect(TEST_DATA_LENGTH);
-        testMarshall_ByteBuffer(bb);
-    }
-
-    private void testMarshall_ByteBuffer(ByteBuffer bb) {
-        // Ensure that Parcel respects the starting offset by not starting at 0
-        bb.position(1);
-        bb.mark();
-
-        // Parcel test data, then marshall into the ByteBuffer
-        Parcel p1 = Parcel.obtain();
-        p1.writeByteArray(TEST_DATA);
-        p1.marshall(bb);
-        p1.recycle();
-
-        assertTrue(bb.position() > 1);
-        bb.reset();
-
-        // Unmarshall test data into a new Parcel
-        Parcel p2 = Parcel.obtain();
-        bb.reset();
-        p2.unmarshall(bb);
-        assertTrue(bb.position() > 1);
-        p2.setDataPosition(0);
-        byte[] marshalled = p2.marshall();
-
-        bb.reset();
-        for (int i = 0; i < TEST_DATA.length; i++) {
-            assertEquals(bb.get(), marshalled[i]);
-        }
-
-        byte[] testDataCopy = new byte[TEST_DATA.length];
-        p2.setDataPosition(0);
-        p2.readByteArray(testDataCopy);
-        for (int i = 0; i < TEST_DATA.length; i++) {
-            assertEquals(TEST_DATA[i], testDataCopy[i]);
-        }
-
-        // Test that overflowing the buffer throws an exception
-        bb.reset();
-        // Leave certainly not enough room for the test data
-        bb.limit(bb.position() + TEST_DATA.length - 1);
-        assertThrows(BufferOverflowException.class, () -> p2.marshall(bb));
-        p2.recycle();
-    }
 }
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
index 9bb51a8..ef30d89 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
@@ -34,6 +34,7 @@
         android:src="@drawable/bubble_ic_settings"/>
 
     <TextView
+        android:id="@+id/education_manage_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/bubble_popup_text_margin"
@@ -45,6 +46,7 @@
         android:text="@string/bubble_bar_education_manage_title"/>
 
     <TextView
+        android:id="@+id/education_manage_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/bubble_popup_text_margin"
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
index 1616707..9076d6a 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
@@ -34,6 +34,7 @@
         android:src="@drawable/ic_floating_landscape"/>
 
     <TextView
+        android:id="@+id/education_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/bubble_popup_text_margin"
@@ -45,6 +46,7 @@
         android:text="@string/bubble_bar_education_stack_title"/>
 
     <TextView
+        android:id="@+id/education_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/bubble_popup_text_margin"
diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
index 225303b..17ebac9 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
@@ -40,6 +40,7 @@
             android:tint="@color/bubbles_icon_tint"/>
 
         <TextView
+            android:id="@+id/manage_dismiss"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginStart="16dp"
@@ -67,6 +68,7 @@
             android:tint="@color/bubbles_icon_tint"/>
 
         <TextView
+            android:id="@+id/manage_dont_bubble"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginStart="16dp"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 426c3ee..290ef16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -27,6 +27,7 @@
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
+import static com.android.wm.shell.shared.TypefaceUtils.setTypeface;
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
@@ -71,6 +72,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.AlphaOptimizedButton;
 import com.android.wm.shell.shared.TriangleShape;
+import com.android.wm.shell.shared.TypefaceUtils;
 import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
 import com.android.wm.shell.taskview.TaskView;
 
@@ -551,6 +553,7 @@
         mManageButton = (AlphaOptimizedButton) LayoutInflater.from(ctw).inflate(
                 R.layout.bubble_manage_button, this /* parent */, false /* attach */);
         addView(mManageButton);
+        setTypeface(mManageButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE);
         mManageButton.setVisibility(visibility);
         setManageClickListener();
         post(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
index da6948d..92007a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
@@ -50,6 +50,7 @@
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.shared.TriangleShape;
+import com.android.wm.shell.shared.TypefaceUtils;
 
 /**
  * Flyout view that appears as a 'chat bubble' alongside the bubble stack. The flyout can visually
@@ -165,8 +166,10 @@
         LayoutInflater.from(context).inflate(R.layout.bubble_flyout, this, true);
         mFlyoutTextContainer = findViewById(R.id.bubble_flyout_text_container);
         mSenderText = findViewById(R.id.bubble_flyout_name);
+        TypefaceUtils.setTypeface(mSenderText, TypefaceUtils.FontFamily.GSF_LABEL_LARGE);
         mSenderAvatar = findViewById(R.id.bubble_flyout_avatar);
         mMessageText = mFlyoutTextContainer.findViewById(R.id.bubble_flyout_text);
+        TypefaceUtils.setTypeface(mMessageText, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM);
 
         final Resources res = getResources();
         mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index 64f54b8..e901e0c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -46,6 +46,7 @@
 import com.android.internal.util.ContrastColorUtil;
 import com.android.wm.shell.Flags;
 import com.android.wm.shell.R;
+import com.android.wm.shell.shared.TypefaceUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -234,6 +235,10 @@
         setBackgroundColor(bgColor);
         mEmptyStateTitle.setTextColor(textColor);
         mEmptyStateSubtitle.setTextColor(textColor);
+        TypefaceUtils.setTypeface(mEmptyStateTitle,
+                TypefaceUtils.FontFamily.GSF_BODY_MEDIUM_EMPHASIZED);
+        TypefaceUtils.setTypeface(mEmptyStateSubtitle, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM);
+
     }
 
     public void updateFontSize() {
@@ -322,6 +327,7 @@
 
         TextView viewName = overflowView.findViewById(R.id.bubble_view_name);
         viewName.setTextColor(textColor);
+        TypefaceUtils.setTypeface(viewName, TypefaceUtils.FontFamily.GSF_LABEL_LARGE);
 
         return new ViewHolder(overflowView, mPositioner);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index dd5a23a..3dce456 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -89,6 +89,8 @@
 import com.android.wm.shell.bubbles.animation.StackAnimationController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.shared.TypefaceUtils;
+import com.android.wm.shell.shared.TypefaceUtils.FontFamily;
 import com.android.wm.shell.shared.animation.Interpolators;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
 import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
@@ -1397,6 +1399,14 @@
         // The menu itself should respect locale direction so the icons are on the correct side.
         mManageMenu.setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
         addView(mManageMenu);
+
+        // Doesn't seem to work unless view is added; so set font after.
+        TypefaceUtils.setTypeface(findViewById(R.id.manage_dismiss), FontFamily.GSF_LABEL_LARGE);
+        TypefaceUtils.setTypeface(findViewById(R.id.manage_dont_bubble),
+                FontFamily.GSF_LABEL_LARGE);
+        TypefaceUtils.setTypeface(mManageSettingsText, FontFamily.GSF_LABEL_LARGE);
+        TypefaceUtils.setTypeface(findViewById(R.id.bubble_manage_menu_fullscreen_title),
+                FontFamily.GSF_LABEL_LARGE);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index 39a2a7b..d2ad708 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -27,6 +27,7 @@
 import android.widget.LinearLayout
 import com.android.internal.R.color.system_neutral1_900
 import com.android.wm.shell.R
+import com.android.wm.shell.shared.TypefaceUtils
 import com.android.wm.shell.shared.animation.Interpolators
 
 /**
@@ -53,6 +54,12 @@
 
     init {
         LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this)
+        TypefaceUtils.setTypeface(findViewById(R.id.user_education_title),
+            TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED)
+        TypefaceUtils.setTypeface(findViewById(R.id.user_education_description),
+            TypefaceUtils.FontFamily.GSF_BODY_MEDIUM)
+        TypefaceUtils.setTypeface(manageButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE_EMPHASIZED)
+        TypefaceUtils.setTypeface(gotItButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE_EMPHASIZED)
         visibility = View.GONE
         elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 1660619..9ac05989 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -26,6 +26,7 @@
 import android.widget.TextView
 import com.android.internal.util.ContrastColorUtil
 import com.android.wm.shell.R
+import com.android.wm.shell.shared.TypefaceUtils
 import com.android.wm.shell.shared.animation.Interpolators
 
 /**
@@ -59,6 +60,9 @@
 
     init {
         LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this)
+        TypefaceUtils.setTypeface(titleTextView,
+            TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED)
+        TypefaceUtils.setTypeface(descTextView, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM)
 
         visibility = View.GONE
         elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
index 6c14d83..bccc6dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
@@ -25,6 +25,7 @@
 import android.widget.TextView;
 
 import com.android.wm.shell.R;
+import com.android.wm.shell.shared.TypefaceUtils;
 
 /**
  * Bubble bar expanded view menu item view to display menu action details
@@ -55,6 +56,7 @@
         super.onFinishInflate();
         mImageView = findViewById(R.id.bubble_bar_menu_item_icon);
         mTextView = findViewById(R.id.bubble_bar_menu_item_title);
+        TypefaceUtils.setTypeface(mTextView, TypefaceUtils.FontFamily.GSF_TITLE_MEDIUM);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
index dfbf655..7c0f8e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -33,6 +33,7 @@
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubble;
+import com.android.wm.shell.shared.TypefaceUtils;
 
 import java.util.ArrayList;
 
@@ -75,6 +76,7 @@
         mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section);
         mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon);
         mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title);
+        TypefaceUtils.setTypeface(mBubbleTitleView, TypefaceUtils.FontFamily.GSF_TITLE_MEDIUM);
         mBubbleDismissIconView = findViewById(R.id.bubble_bar_manage_menu_dismiss_icon);
         updateThemeColors();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
index 7adec39..0bd3a54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
@@ -35,6 +35,7 @@
 import com.android.wm.shell.bubbles.BubbleEducationController
 import com.android.wm.shell.bubbles.BubbleViewProvider
 import com.android.wm.shell.bubbles.setup
+import com.android.wm.shell.shared.TypefaceUtils
 import com.android.wm.shell.shared.animation.PhysicsAnimator
 import com.android.wm.shell.shared.bubbles.BubblePopupDrawable
 import com.android.wm.shell.shared.bubbles.BubblePopupView
@@ -108,6 +109,10 @@
         root.getBoundsOnScreen(rootBounds)
         educationView =
             createEducationView(R.layout.bubble_bar_stack_education, root).apply {
+                TypefaceUtils.setTypeface(findViewById(R.id.education_title),
+                    TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED)
+                TypefaceUtils.setTypeface(findViewById(R.id.education_text),
+                    TypefaceUtils.FontFamily.GSF_BODY_MEDIUM)
                 setArrowDirection(BubblePopupDrawable.ArrowDirection.DOWN)
                 updateEducationPosition(view = this, position, rootBounds)
                 val arrowToEdgeOffset = popupDrawable?.config?.cornerRadius ?: 0f
@@ -153,6 +158,10 @@
 
         educationView =
             createEducationView(R.layout.bubble_bar_manage_education, root).apply {
+                TypefaceUtils.setTypeface(findViewById(R.id.education_manage_title),
+                    TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED)
+                TypefaceUtils.setTypeface(findViewById(R.id.education_manage_text),
+                    TypefaceUtils.FontFamily.GSF_BODY_MEDIUM)
                 pivotY = 0f
                 doOnLayout { it.pivotX = it.width / 2f }
                 setOnClickListener { hideEducation(animated = true) }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index dd5827a..320de2a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -142,8 +142,8 @@
 ## Tracing activity starts & finishes in the app process
 
 It's sometimes useful to know when to see a stack trace of when an activity starts in the app code
-(ie. if you are repro'ing a bug related to activity starts). You can enable this system property to
-get this trace:
+or via a `WindowContainerTransaction` (ie. if you are repro'ing a bug related to activity starts).
+You can enable this system property to get this trace:
 ```shell
 # Enabling
 adb shell setprop persist.wm.debug.start_activity true
@@ -168,6 +168,21 @@
 adb reboot
 ```
 
+## Tracing transition requests in the Shell
+
+To trace where a new WM transition is started in the Shell, you can enable this system property:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.start_shell_transition true
+adb reboot
+adb logcat -s "ShellTransitions"
+
+# Disabling
+adb shell setprop persist.wm.debug.start_shell_transition \"\"
+adb reboot
+```
+
+
 ## Dumps
 
 Because the Shell library is built as a part of SystemUI, dumping the state is currently done as a
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index e28a7fa..003ef1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -39,6 +39,7 @@
 
 import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived;
 import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
 import static com.android.wm.shell.shared.TransitionUtil.FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY;
 import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
 import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
@@ -52,6 +53,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -138,6 +140,10 @@
         ShellCommandHandler.ShellCommandActionHandler {
     static final String TAG = "ShellTransitions";
 
+    // If set, will print the stack trace for transition starts within the process
+    static final boolean DEBUG_START_TRANSITION = Build.IS_DEBUGGABLE &&
+            SystemProperties.getBoolean("persist.wm.debug.start_shell_transition", false);
+
     /** Set to {@code true} to enable shell transitions. */
     public static final boolean ENABLE_SHELL_TRANSITIONS = getShellTransitEnabled();
     public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
@@ -346,10 +352,10 @@
         mShellController = shellController;
         // The very last handler (0 in the list) should be the default one.
         mHandlers.add(mDefaultTransitionHandler);
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "addHandler: Default");
         // Next lowest priority is remote transitions.
         mHandlers.add(mRemoteTransitionHandler);
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "addHandler: Remote");
         shellInit.addInitCallback(this::onInit, this);
         mHomeTransitionObserver = homeTransitionObserver;
         mFocusTransitionObserver = focusTransitionObserver;
@@ -439,7 +445,7 @@
         mHandlers.add(handler);
         // Set initial scale settings.
         handler.setAnimScaleSetting(mTransitionAnimationScaleSetting);
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: %s",
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "addHandler: %s",
                 handler.getClass().getSimpleName());
     }
 
@@ -691,7 +697,7 @@
     void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
         info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
                 info.getDebugId(), transitionToken, info.toString("    " /* prefix */));
         int activeIdx = findByToken(mPendingTransitions, transitionToken);
         if (activeIdx < 0) {
@@ -753,7 +759,7 @@
                 if (tr.isIdle()) continue;
                 hadPreceding = true;
                 // Sleep starts a process of forcing all prior transitions to finish immediately
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                ProtoLog.v(WM_SHELL_TRANSITIONS,
                         "Start finish-for-sync track %d", i);
                 finishForSync(active.mToken, i, null /* forceFinish */);
             }
@@ -797,7 +803,7 @@
         if (info.getRootCount() == 0 && !KeyguardTransitionHandler.handles(info)) {
             // No root-leashes implies that the transition is empty/no-op, so just do
             // housekeeping and return.
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots in %s so"
+            ProtoLog.v(WM_SHELL_TRANSITIONS, "No transition roots in %s so"
                     + " abort", active);
             onAbort(active);
             return true;
@@ -839,7 +845,7 @@
                 && allOccluded)) {
             // Treat this as an abort since we are bypassing any merge logic and effectively
             // finishing immediately.
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+            ProtoLog.v(WM_SHELL_TRANSITIONS,
                     "Non-visible anim so abort: %s", active);
             onAbort(active);
             return true;
@@ -873,7 +879,7 @@
     void processReadyQueue(Track track) {
         if (track.mReadyTransitions.isEmpty()) {
             if (track.mActiveTransition == null) {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Track %d became idle",
+                ProtoLog.v(WM_SHELL_TRANSITIONS, "Track %d became idle",
                         mTracks.indexOf(track));
                 if (areTracksIdle()) {
                     if (!mReadyDuringSync.isEmpty()) {
@@ -885,7 +891,7 @@
                             if (!success) break;
                         }
                     } else if (mPendingTransitions.isEmpty()) {
-                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition "
+                        ProtoLog.v(WM_SHELL_TRANSITIONS, "All active transition "
                                 + "animations finished");
                         mKnownTransitions.clear();
                         // Run all runnables from the run-when-idle queue.
@@ -926,7 +932,7 @@
             onMerged(playingToken, readyToken);
             return;
         }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition %s ready while"
                 + " %s is still animating. Notify the animating transition"
                 + " in case they can be merged", ready, playing);
         mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
@@ -955,7 +961,7 @@
         }
 
         final Track track = mTracks.get(playing.getTrack());
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s",
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s",
                 merged, playing);
         int readyIdx = 0;
         if (track.mReadyTransitions.isEmpty() || track.mReadyTransitions.get(0) != merged) {
@@ -996,7 +1002,7 @@
     }
 
     private void playTransition(@NonNull ActiveTransition active) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Playing animation for %s", active);
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "Playing animation for %s", active);
         final var token = active.mToken;
 
         for (int i = 0; i < mObservers.size(); ++i) {
@@ -1007,12 +1013,12 @@
 
         // If a handler already chose to run this animation, try delegating to it first.
         if (active.mHandler != null) {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
+            ProtoLog.v(WM_SHELL_TRANSITIONS, " try firstHandler %s",
                     active.mHandler);
             boolean consumed = active.mHandler.startAnimation(token, active.mInfo,
                     active.mStartT, active.mFinishT, (wct) -> onFinish(token, wct));
             if (consumed) {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
+                ProtoLog.v(WM_SHELL_TRANSITIONS, " animated by firstHandler");
                 mTransitionTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
                 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
                     Trace.instant(TRACE_TAG_WINDOW_MANAGER,
@@ -1042,14 +1048,14 @@
     ) {
         for (int i = mHandlers.size() - 1; i >= 0; --i) {
             if (mHandlers.get(i) == skip) {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " skip handler %s",
+                ProtoLog.v(WM_SHELL_TRANSITIONS, " skip handler %s",
                         mHandlers.get(i));
                 continue;
             }
             boolean consumed = mHandlers.get(i).startAnimation(transition, info, startT, finishT,
                     finishCB);
             if (consumed) {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
+                ProtoLog.v(WM_SHELL_TRANSITIONS, " animated by %s",
                         mHandlers.get(i));
                 mTransitionTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
                 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
@@ -1155,7 +1161,7 @@
         for (int i = 0; i < mObservers.size(); ++i) {
             mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
         }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished "
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition animation finished "
                 + "(aborted=%b), notifying core %s", active.mAborted, active);
         if (active.mStartT != null) {
             // Applied by now, so clear immediately to remove any references. Do not set to null
@@ -1209,7 +1215,7 @@
 
     void requestStartTransition(@NonNull IBinder transitionToken,
             @Nullable TransitionRequestInfo request) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s",
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s",
                 request.getDebugId(), transitionToken, request);
         if (mKnownTransitions.containsKey(transitionToken)) {
             throw new RuntimeException("Transition already started " + transitionToken);
@@ -1228,6 +1234,8 @@
             if (requestResult != null) {
                 active.mHandler = requestResult.first;
                 wct = requestResult.second;
+                ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition (#%d): request handled by %s",
+                        request.getDebugId(), active.mHandler.getClass().getSimpleName());
             }
             if (request.getDisplayChange() != null) {
                 TransitionRequestInfo.DisplayChange change = request.getDisplayChange();
@@ -1273,8 +1281,12 @@
      */
     public IBinder startTransition(@WindowManager.TransitionType int type,
             @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition "
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "Directly starting a new transition "
                 + "type=%s wct=%s handler=%s", transitTypeToString(type), wct, handler);
+        if (DEBUG_START_TRANSITION) {
+            Log.d(TAG, "startTransition: type=" + transitTypeToString(type)
+                    + " wct=" + wct + " handler=" + handler.getClass().getName(), new Throwable());
+        }
         final ActiveTransition active =
                 new ActiveTransition(mOrganizer.startNewTransition(type, wct));
         active.mHandler = handler;
@@ -1362,7 +1374,7 @@
             }
             // Attempt to merge a SLEEP info to signal that the playing transition needs to
             // fast-forward.
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge sync %s"
+            ProtoLog.v(WM_SHELL_TRANSITIONS, " Attempt to merge sync %s"
                     + " into %s via a SLEEP proxy", nextSync, playing);
             playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT, dummyT,
                     playing.mToken, (wct) -> {});
@@ -1598,7 +1610,7 @@
         public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo,
                 SurfaceControl.Transaction t, SurfaceControl.Transaction finishT)
                 throws RemoteException {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady(transaction=%d)",
+            ProtoLog.v(WM_SHELL_TRANSITIONS, "onTransitionReady(transaction=%d)",
                     t.getId());
             mMainExecutor.execute(() -> Transitions.this.onTransitionReady(
                     iBinder, transitionInfo, t, finishT));
@@ -1784,8 +1796,9 @@
         pw.println(prefix + TAG);
 
         final String innerPrefix = prefix + "  ";
-        pw.println(prefix + "Handlers:");
-        for (TransitionHandler handler : mHandlers) {
+        pw.println(prefix + "Handlers (ordered by priority):");
+        for (int i = mHandlers.size() - 1; i >= 0; i--) {
+            final TransitionHandler handler = mHandlers.get(i);
             pw.print(innerPrefix);
             pw.print(handler.getClass().getSimpleName());
             pw.println(" (" + Integer.toHexString(System.identityHashCode(handler)) + ")");
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index dc669a5..aa8cbd1 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -175,6 +175,8 @@
     if (stream->isValid()) {
         mOpenMultiPicStream = std::move(stream);
         mSerialContext.reset(new SkSharingSerialContext());
+        // passing the GrDirectContext to the SerialContext allows us to raster/serialize GPU images
+        mSerialContext->setDirectContext(mRenderThread.getGrContext());
         SkSerialProcs procs;
         procs.fImageProc = SkSharingSerialContext::serializeImage;
         procs.fImageCtx = mSerialContext.get();
diff --git a/packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.xml b/packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.xml
new file mode 100644
index 0000000..6b534aa
--- /dev/null
+++ b/packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="34dp"
+    android:height="42dp"
+    android:viewportWidth="34"
+    android:viewportHeight="42">
+    <path
+        android:pathData="M0.856,17.569C0.887,19.083 1.004,20.593 1.206,22.094C2.166,28.584 5.804,35.937 15.774,41.089C16.171,41.293 16.61,41.4 17.056,41.4C17.503,41.4 17.942,41.293 18.339,41.089C28.309,35.936 31.947,28.583 32.907,22.093C33.109,20.593 33.226,19.083 33.256,17.569V8.605C33.257,7.919 33.046,7.25 32.652,6.688C32.259,6.127 31.703,5.7 31.059,5.467L18.191,0.8C17.458,0.534 16.655,0.534 15.922,0.8L3.054,5.467C2.41,5.7 1.854,6.127 1.461,6.688C1.067,7.25 0.856,7.919 0.856,8.605V17.569Z"
+        android:fillColor="#D1C2CB"/>
+    <path
+        android:pathData="M15.067,24.333V10.733H18.933V24.333H15.067ZM15.067,31.267V27.433H18.933V31.267H15.067Z"
+        android:fillColor="@color/settingslib_materialColorSurfaceContainerLowest"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml b/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml
index 54860d4..deda258 100644
--- a/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml
+++ b/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml
@@ -21,6 +21,7 @@
             <enum name="low" value="1"/>
             <enum name="medium" value="2"/>
             <enum name="high" value="3"/>
+            <enum name="off" value="4"/>
         </attr>
         <attr name="buttonLevel" format="enum">
             <enum name="generic" value="0"/>
diff --git a/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml b/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml
index 19181dd..abc458b 100644
--- a/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml
+++ b/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml
@@ -22,4 +22,5 @@
     <color name="settingslib_expressive_color_status_level_medium">#FCBD00</color>
     <!-- static palette red50 -->
     <color name="settingslib_expressive_color_status_level_high">#DB372D</color>
+    <color name="settingslib_expressive_color_status_level_off">#D1C2CB</color>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt
index 1f8cfb5..eda281c 100644
--- a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt
+++ b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt
@@ -40,7 +40,8 @@
         GENERIC,
         LOW,
         MEDIUM,
-        HIGH
+        HIGH,
+        OFF
     }
     var iconLevel: BannerStatus = BannerStatus.GENERIC
         set(value) {
@@ -87,6 +88,7 @@
         1 -> BannerStatus.LOW
         2 -> BannerStatus.MEDIUM
         3 -> BannerStatus.HIGH
+        4 -> BannerStatus.OFF
         else -> BannerStatus.GENERIC
     }
 
@@ -104,7 +106,10 @@
         }
 
         (holder.findViewById(R.id.status_banner_button) as? MaterialButton)?.apply {
-            setBackgroundColor(getBackgroundColor(buttonLevel))
+            setBackgroundColor(
+                if (buttonLevel == BannerStatus.OFF) getBackgroundColor(BannerStatus.GENERIC)
+                else getBackgroundColor(buttonLevel)
+            )
             text = buttonText
             setOnClickListener(listener)
             visibility = if (listener != null) View.VISIBLE else View.GONE
@@ -143,6 +148,11 @@
                 R.color.settingslib_expressive_color_status_level_high
             )
 
+            BannerStatus.OFF -> ContextCompat.getColor(
+                context,
+                R.color.settingslib_expressive_color_status_level_off
+            )
+
             else -> ContextCompat.getColor(
                 context,
                 com.android.settingslib.widget.theme.R.color.settingslib_materialColorPrimary
@@ -167,6 +177,11 @@
                 R.drawable.settingslib_expressive_icon_status_level_high
             )
 
+            BannerStatus.OFF -> ContextCompat.getDrawable(
+                context,
+                R.drawable.settingslib_expressive_icon_status_level_off
+            )
+
             else -> null
         }
     }
@@ -188,6 +203,7 @@
                 R.drawable.settingslib_expressive_background_level_high
             )
 
+            // GENERIC and OFF are using the same background drawable.
             else -> ContextCompat.getDrawable(
                 context,
                 R.drawable.settingslib_expressive_background_generic
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 1362ffe..86559fd 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -1,22 +1,6 @@
 {
-  // Curious where your @Scenario tests are running?
-  //
-  // @Ignore: Will not run in any configuration
-  //
-  // @FlakyTest: Tests that don't block pre/postsubmit but are staged to run known failures.
-  //             Tests will run in postsubmit on sysui-e2e-staged suite.
-  //
-  //
-  // @PlatinumTest: Marking your test with this annotation will put your tests in presubmit.
-  //                Please DO NOT annotate new or old tests with @PlatinumTest annotation
-  //                without discussing with mdb:android-platinum
-  //
-  // @Postsubmit: Do not use this annotation for e2e tests. This won't have any affect.
-
-  // For all other e2e tests which are not platinum, they run in sysui-silver suite,that
-  // primarily runs in postsubmit with an exception to e2e test related changes.
-  // If you want to see one shot place to monitor all e2e tests, look for
-  // sysui-e2e-staged suite.
+  // Test mappings for SystemUI unit tests.
+  // For e2e mappings, see go/sysui-e2e-test-mapping
 
   // v2/android-virtual-infra/test_mapping/presubmit-avd
   "presubmit": [
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 228bff6..4693377 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -2021,14 +2021,7 @@
 flag {
   name: "permission_helper_ui_rich_ongoing"
   namespace: "systemui"
-  description: "[RONs] Guards inline permission helper for demoting RONs [Guts/card version]"
-  bug: "379186372"
-}
-
-flag {
-  name: "permission_helper_inline_ui_rich_ongoing"
-  namespace: "systemui"
-  description: "[RONs] Guards inline permission helper for demoting RONs [Inline version]"
+  description: "[RONs] Guards inline permission helper for demoting RONs"
   bug: "379186372"
 }
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
index ca94482..21ec896 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
@@ -245,6 +245,17 @@
                 runner.onAnimationCancelled();
                 finishRunnable.run();
             }
+
+            @Override
+            public void onTransitionConsumed(IBinder transition, boolean aborted)
+                    throws RemoteException {
+                // Notify the remote runner that the transition has been canceled if the transition
+                // was merged into another transition or aborted
+                synchronized (mFinishRunnables) {
+                    mFinishRunnables.remove(transition);
+                }
+                runner.onAnimationCancelled();
+            }
         };
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt
index 8efc031..12ade62 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt
@@ -365,6 +365,22 @@
 
     @Test
     @EnableFlags(NotificationBundleUi.FLAG_NAME)
+    fun onDragSuccess() {
+        val notification: Notification =
+            Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .addAction(mock(Notification.Action::class.java))
+                .build()
+        val entry = NotificationEntryBuilder().setNotification(notification).build()
+
+        underTest = factory.create(entry) as NotificationEntryAdapter
+
+        underTest.onDragSuccess()
+        verify(kosmos.mockNotificationActivityStarter).onDragSuccess(entry)
+    }
+
+    @Test
+    @EnableFlags(NotificationBundleUi.FLAG_NAME)
     fun onNotificationBubbleIconClicked() {
         val notification: Notification =
             Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index 4019c70..ce120c5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -101,6 +101,46 @@
         row.resetMenu();
     }
 
+
+    @Test
+    public void testNoAppOpsInSlowSwipe() {
+        when(mRow.getShowSnooze()).thenReturn(false);
+        Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
+
+        NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
+        row.createMenu(mRow);
+
+        ViewGroup container = (ViewGroup) row.getMenuView();
+        // noti blocking
+        assertEquals(1, container.getChildCount());
+    }
+
+    @Test
+    public void testNoSnoozeInSlowSwipe() {
+        when(mRow.getShowSnooze()).thenReturn(false);
+        Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
+
+        NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
+        row.createMenu(mRow);
+
+        ViewGroup container = (ViewGroup) row.getMenuView();
+        // just for noti blocking
+        assertEquals(1, container.getChildCount());
+    }
+
+    @Test
+    public void testSnoozeInSlowSwipe() {
+        when(mRow.getShowSnooze()).thenReturn(true);
+        Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
+
+        NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
+        row.createMenu(mRow);
+
+        ViewGroup container = (ViewGroup) row.getMenuView();
+        // one for snooze and one for noti blocking
+        assertEquals(2, container.getChildCount());
+    }
+
     @Test
     public void testSlowSwipe_newDismiss() {
         when(mRow.getShowSnooze()).thenReturn(true);
@@ -195,7 +235,6 @@
                 new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
         doReturn(30f).when(row).getSnapBackThreshold();
         doReturn(50f).when(row).getDismissThreshold();
-        doReturn(70).when(row).getSpaceForMenu();
 
         when(row.isMenuOnLeft()).thenReturn(true);
         when(row.getTranslation()).thenReturn(40f);
diff --git a/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml b/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml
index 73704f8..f17cc96 100644
--- a/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml
+++ b/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml
@@ -21,7 +21,7 @@
         android:height="2dp"
         android:width="@dimen/rear_display_progress_width">
         <shape android:shape="rectangle">
-            <solid android:color="@androidprv:color/materialColorSurfaceContainer" />
+            <solid android:color="?android:attr/colorAccent"/>
         </shape>
     </item>
     <item
@@ -29,4 +29,4 @@
         android:gravity="center_vertical|fill_horizontal">
         <com.android.systemui.util.RoundedCornerProgressDrawable android:drawable="@drawable/rear_display_dialog_seekbar_progress" />
     </item>
-</layer-list>
\ No newline at end of file
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/unpin_icon.xml b/packages/SystemUI/res/drawable/unpin_icon.xml
deleted file mode 100644
index 4e2e158..0000000
--- a/packages/SystemUI/res/drawable/unpin_icon.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="960"
-    android:viewportHeight="960"
-    android:tint="?attr/colorControlNormal">
-  <path
-      android:fillColor="@android:color/white"
-      android:pathData="M680,120L680,200L640,200L640,527L560,447L560,200L400,200L400,287L313,200L280,167L280,167L280,120L680,120ZM480,920L440,880L440,640L240,640L240,560L320,480L320,434L56,168L112,112L848,848L790,904L526,640L520,640L520,880L480,920ZM354,560L446,560L402,516L400,514L354,560ZM480,367L480,367L480,367L480,367ZM402,516L402,516L402,516L402,516Z"/>
-</vector>
diff --git a/packages/SystemUI/res/layout/promoted_permission_guts.xml b/packages/SystemUI/res/layout/promoted_permission_guts.xml
deleted file mode 100644
index 50e5ae3c..0000000
--- a/packages/SystemUI/res/layout/promoted_permission_guts.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright 2017, The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-
-<com.android.systemui.statusbar.notification.row.PromotedPermissionGutsContent
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical"
-    android:paddingTop="2dp"
-    android:paddingBottom="2dp"
-    android:background="@androidprv:color/materialColorSurfaceContainerHigh"
-    android:theme="@style/Theme.SystemUI"
-    >
-
-    <RelativeLayout
-        android:id="@+id/promoted_guts"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="@dimen/notification_2025_min_height">
-
-        <ImageView
-            android:id="@+id/unpin_icon"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:src="@drawable/unpin_icon"
-            android:layout_alignParentTop="true"
-            android:layout_centerHorizontal="true"
-            android:padding="@dimen/notification_importance_button_padding"
-            />
-
-        <TextView
-            android:id="@+id/demote_explain"
-            android:layout_width="400dp"
-            android:layout_height="wrap_content"
-            android:layout_alignParentLeft="true"
-            android:layout_below="@id/unpin_icon"
-            android:layout_toLeftOf="@id/undo"
-            android:padding="@*android:dimen/notification_content_margin_end"
-            android:textColor="@androidprv:color/materialColorOnSurface"
-            android:minWidth="@dimen/min_clickable_item_size"
-            android:minHeight="@dimen/min_clickable_item_size"
-            style="@style/TextAppearance.NotificationInfo.Button" />
-
-        <TextView
-            android:id="@+id/undo"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_below="@id/unpin_icon"
-            android:layout_alignParentRight="true"
-            android:padding="@*android:dimen/notification_content_margin_end"
-            android:textColor="@androidprv:color/materialColorOnSurface"
-            android:minWidth="@dimen/min_clickable_item_size"
-            android:minHeight="@dimen/min_clickable_item_size"
-            android:text="@string/snooze_undo"
-            style="@style/TextAppearance.NotificationInfo.Button" />
-    </RelativeLayout>
-
-</com.android.systemui.statusbar.notification.row.PromotedPermissionGutsContent>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7ef03ae..3fdb98b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -4195,12 +4195,6 @@
         All Quick Settings tiles will reset to the device’s original settings
     </string>
 
-
-    <!-- Content of the Reset Tiles dialog in QS Edit mode. [CHAR LIMIT=NONE] -->
-    <string name="demote_explain_text">
-         <xliff:g id="application" example= "Superfast Food Delivery">%1$s</xliff:g> will no longer show Live Updates here. You can change this any time in Settings.
-    </string>
-
     <!-- Template that joins disabled message with the label for the voice over. [CHAR LIMIT=NONE] -->
     <string name="volume_slider_disabled_message_template"><xliff:g example="Notification" id="stream_name">%1$s</xliff:g>, <xliff:g example="Disabled because ring is muted" id="disabled_message">%2$s</xliff:g></string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index fcdcc3f..3bb1ff1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -23,6 +23,7 @@
 
 import com.android.systemui.DejankUtils;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
@@ -44,13 +45,20 @@
     private final Optional<Bubbles> mBubblesOptional;
     private final NotificationActivityStarter mNotificationActivityStarter;
 
-    private ExpandableNotificationRow.OnDragSuccessListener mOnDragSuccessListener =
-            new ExpandableNotificationRow.OnDragSuccessListener() {
-                @Override
-                public void onDragSuccess(NotificationEntry entry) {
-                    mNotificationActivityStarter.onDragSuccess(entry);
-                }
-            };
+    private ExpandableNotificationRow.OnDragSuccessListener mOnDragSuccessListener
+            = new ExpandableNotificationRow.OnDragSuccessListener() {
+        @Override
+        public void onDragSuccess(NotificationEntry entry) {
+            NotificationBundleUi.assertInLegacyMode();
+            mNotificationActivityStarter.onDragSuccess(entry);
+        }
+
+        @Override
+        public void onDragSuccess(EntryAdapter entryAdapter) {
+            NotificationBundleUi.isUnexpectedlyInLegacyMode();
+            entryAdapter.onDragSuccess();
+        }
+    };
 
     private NotificationClicker(
             NotificationClickerLogger logger,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
index 2967c65..e743d87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
@@ -126,6 +126,11 @@
         return false
     }
 
+    override fun onDragSuccess() {
+        // do nothing. these should not be draggable
+        Log.wtf(TAG, "onDragSuccess() called")
+    }
+
     override fun onNotificationBubbleIconClicked() {
         // do nothing. these cannot be a bubble
         Log.wtf(TAG, "onNotificationBubbleIconClicked() called")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
index dbb5c10..f39bd03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
@@ -144,6 +144,8 @@
         return false;
     }
 
+    void onDragSuccess();
+
     /**
      * Process a click on a notification bubble icon
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
index bda28a9..12cfa91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
@@ -149,6 +149,10 @@
         return entry.sbn.notification.fullScreenIntent != null
     }
 
+    override fun onDragSuccess() {
+        notificationActivityStarter.onDragSuccess(entry)
+    }
+
     override fun onNotificationBubbleIconClicked() {
         notificationActivityStarter.onNotificationBubbleIconClicked(entry)
     }
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 c4fde20..256d549 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
@@ -22,9 +22,9 @@
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_EXPANDED;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
 
+import static com.android.systemui.Flags.notificationRowAccessibilityExpanded;
 import static com.android.systemui.Flags.notificationRowTransparency;
 import static com.android.systemui.Flags.notificationsPinnedHunInShade;
-import static com.android.systemui.Flags.notificationRowAccessibilityExpanded;
 import static com.android.systemui.flags.Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE;
 import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
 import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED;
@@ -2520,7 +2520,11 @@
      */
     public void dragAndDropSuccess() {
         if (mOnDragSuccessListener != null) {
-            mOnDragSuccessListener.onDragSuccess(getEntry());
+            if (NotificationBundleUi.isEnabled()) {
+                mOnDragSuccessListener.onDragSuccess(getEntryAdapter());
+            } else {
+                mOnDragSuccessListener.onDragSuccess(getEntryLegacy());
+            }
         }
     }
 
@@ -2680,58 +2684,30 @@
     }
 
     /**
-     * Whether to allow dismissal with the whole-row translation animation.
-     *
-     * If true, either animation is permissible.
-     * If false, usingRTX behavior is forbidden, only clipping animation should be used.
-     *
-     * Usually either is OK, except for promoted notifications, where we always need to
-     * dismiss with content clipping/partial translation animation instead, so that we
-     * can show the demotion options.
-     * @return
-     */
-    private boolean allowDismissUsingRowTranslationX() {
-        if (Flags.permissionHelperInlineUiRichOngoing()) {
-            return !isPromotedOngoing();
-        } else {
-            // Don't change behavior unless the flag is on.
-            return true;
-        }
-    }
-
-    /**
      * Set the dismiss behavior of the view.
      *
      * @param usingRowTranslationX {@code true} if the view should translate using regular
      *                             translationX, otherwise the contents will be
      *                             translated.
-     * @param forceUpdateChildren {@code true} to force initialization, {@code false} if lazy
-     *                             behavior is OK.
      */
     @Override
-    public void setDismissUsingRowTranslationX(boolean usingRowTranslationX,
-            boolean forceUpdateChildren) {
-        // Before updating dismiss behavior, make sure this is an allowable configuration for this
-        // notification.
-        usingRowTranslationX = usingRowTranslationX && allowDismissUsingRowTranslationX();
-
-        if (forceUpdateChildren || (usingRowTranslationX != mDismissUsingRowTranslationX)) {
+    public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) {
+        if (usingRowTranslationX != mDismissUsingRowTranslationX) {
             // In case we were already transitioning, let's switch over!
             float previousTranslation = getTranslation();
             if (previousTranslation != 0) {
                 setTranslation(0);
             }
-            super.setDismissUsingRowTranslationX(usingRowTranslationX, forceUpdateChildren);
+            super.setDismissUsingRowTranslationX(usingRowTranslationX);
             if (previousTranslation != 0) {
                 setTranslation(previousTranslation);
             }
-
             if (mChildrenContainer != null) {
                 List<ExpandableNotificationRow> notificationChildren =
                         mChildrenContainer.getAttachedChildren();
                 for (int i = 0; i < notificationChildren.size(); i++) {
                     ExpandableNotificationRow child = notificationChildren.get(i);
-                    child.setDismissUsingRowTranslationX(usingRowTranslationX, forceUpdateChildren);
+                    child.setDismissUsingRowTranslationX(usingRowTranslationX);
                 }
             }
         }
@@ -4449,6 +4425,12 @@
          * @param entry NotificationEntry that succeed to drop on proper target window.
          */
         void onDragSuccess(NotificationEntry entry);
+
+        /**
+         * @param entryAdapter The EntryAdapter that successfully dropped on the proper
+         *            target window
+         */
+        void onDragSuccess(EntryAdapter entryAdapter);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 6c990df..80cf818 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -292,8 +292,7 @@
      *                             translationX, otherwise the contents will be
      *                             translated.
      */
-    public void setDismissUsingRowTranslationX(boolean usingRowTranslationX,
-            boolean forceUpdateChildren) {
+    public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) {
         mDismissUsingRowTranslationX = usingRowTranslationX;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index f4e01bf..cdb78d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -29,7 +29,6 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -310,7 +309,6 @@
         });
 
         View gutsView = item.getGutsView();
-
         try {
             if (gutsView instanceof NotificationSnooze) {
                 initializeSnoozeView(row, (NotificationSnooze) gutsView);
@@ -324,8 +322,6 @@
                         (PartialConversationInfo) gutsView);
             } else if (gutsView instanceof FeedbackInfo) {
                 initializeFeedbackInfo(row, (FeedbackInfo) gutsView);
-            } else if (gutsView instanceof PromotedPermissionGutsContent) {
-                initializeDemoteView(row, (PromotedPermissionGutsContent) gutsView);
             }
             return true;
         } catch (Exception e) {
@@ -355,31 +351,6 @@
     }
 
     /**
-     * Sets up the {@link NotificationSnooze} inside the notification row's guts.
-     *
-     * @param row view to set up the guts for
-     * @param demoteGuts view to set up/bind within {@code row}
-     */
-    private void initializeDemoteView(
-            final ExpandableNotificationRow row,
-            PromotedPermissionGutsContent demoteGuts) {
-        StatusBarNotification sbn = row.getEntry().getSbn();
-        demoteGuts.setStatusBarNotification(sbn);
-        demoteGuts.setOnDemoteAction(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                try {
-                    // TODO(b/391661009): Signal AutomaticPromotionCoordinator here
-                    mNotificationManager.setCanBePromoted(
-                            sbn.getPackageName(), sbn.getUid(), false, true);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Couldn't revoke live update permission", e);
-                }
-            }
-        });
-    }
-
-    /**
      * Sets up the {@link FeedbackInfo} inside the notification row's guts.
      *
      * @param row view to set up the guts for
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 da8e44a..977936f 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
@@ -273,7 +273,6 @@
         } else if (personNotifType >= PeopleNotificationIdentifier.TYPE_FULL_PERSON) {
             mInfoItem = createConversationItem(mContext);
         } else if (android.app.Flags.uiRichOngoing()
-                && android.app.Flags.apiRichOngoing()
                 && Flags.permissionHelperUiRichOngoing()
                 && sbn.getNotification().isPromotedOngoing()) {
             mInfoItem = createPromotedItem(mContext);
@@ -286,12 +285,6 @@
         }
         mRightMenuItems.add(mInfoItem);
         mRightMenuItems.add(mFeedbackItem);
-        if (android.app.Flags.uiRichOngoing() && Flags.permissionHelperInlineUiRichOngoing()
-                && entry.isPromotedOngoing()) {
-            mRightMenuItems.add(createDemoteItem(mContext));
-        }
-
-
         mLeftMenuItems.addAll(mRightMenuItems);
 
         populateMenuViews();
@@ -313,19 +306,15 @@
         } else {
             mMenuContainer = new FrameLayout(mContext);
         }
-
         final int showDismissSetting =  Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.SHOW_NEW_NOTIF_DISMISS, /* default = */ 1);
         final boolean newFlowHideShelf = showDismissSetting == 1;
-
-        // Populate menu items if we are using the new permission helper (U+) or if we are using
-        // the very old dismiss setting (SC-).
-        // TODO: SHOW_NEW_NOTIF_DISMISS==0 case can likely be removed.
-        if (Flags.permissionHelperInlineUiRichOngoing() || !newFlowHideShelf) {
-            List<MenuItem> menuItems = mOnLeft ? mLeftMenuItems : mRightMenuItems;
-            for (int i = 0; i < menuItems.size(); i++) {
-                addMenuView(menuItems.get(i), mMenuContainer);
-            }
+        if (newFlowHideShelf) {
+            return;
+        }
+        List<MenuItem> menuItems = mOnLeft ? mLeftMenuItems : mRightMenuItems;
+        for (int i = 0; i < menuItems.size(); i++) {
+            addMenuView(menuItems.get(i), mMenuContainer);
         }
     }
 
@@ -689,15 +678,6 @@
         return snooze;
     }
 
-    static MenuItem createDemoteItem(Context context) {
-        PromotedPermissionGutsContent demoteContent =
-                (PromotedPermissionGutsContent) LayoutInflater.from(context).inflate(
-                R.layout.promoted_permission_guts, null, false);
-        MenuItem info = new NotificationMenuItem(context, null, demoteContent,
-                R.drawable.unpin_icon);
-        return info;
-    }
-
     static NotificationMenuItem createConversationItem(Context context) {
         Resources res = context.getResources();
         String infoDescription = res.getString(R.string.notification_menu_gear_description);
@@ -705,7 +685,7 @@
                 (NotificationConversationInfo) LayoutInflater.from(context).inflate(
                         R.layout.notification_conversation_info, null, false);
         return new NotificationMenuItem(context, infoDescription, infoContent,
-                NotificationMenuItem.OMIT_FROM_SWIPE_MENU);
+                R.drawable.ic_settings);
     }
 
     static NotificationMenuItem createPromotedItem(Context context) {
@@ -715,7 +695,7 @@
                 (PromotedNotificationInfo) LayoutInflater.from(context).inflate(
                         R.layout.promoted_notification_info, null, false);
         return new NotificationMenuItem(context, infoDescription, infoContent,
-                NotificationMenuItem.OMIT_FROM_SWIPE_MENU);
+                R.drawable.ic_settings);
     }
 
     static NotificationMenuItem createPartialConversationItem(Context context) {
@@ -725,7 +705,7 @@
                 (PartialConversationInfo) LayoutInflater.from(context).inflate(
                         R.layout.partial_conversation_info, null, false);
         return new NotificationMenuItem(context, infoDescription, infoContent,
-                NotificationMenuItem.OMIT_FROM_SWIPE_MENU);
+                R.drawable.ic_settings);
     }
 
     static NotificationMenuItem createInfoItem(Context context) {
@@ -737,14 +717,14 @@
         NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate(
                 layoutId, null, false);
         return new NotificationMenuItem(context, infoDescription, infoContent,
-                NotificationMenuItem.OMIT_FROM_SWIPE_MENU);
+                R.drawable.ic_settings);
     }
 
     static MenuItem createFeedbackItem(Context context) {
         FeedbackInfo feedbackContent = (FeedbackInfo) LayoutInflater.from(context).inflate(
                 R.layout.feedback_info, null, false);
         MenuItem info = new NotificationMenuItem(context, null, feedbackContent,
-                NotificationMenuItem.OMIT_FROM_SWIPE_MENU);
+                -1 /*don't show in slow swipe menu */);
         return info;
     }
 
@@ -781,10 +761,6 @@
 
     @Override
     public boolean isWithinSnapMenuThreshold() {
-        if (getSpaceForMenu() == 0) {
-            // don't snap open if there are no items
-            return false;
-        }
         float translation = getTranslation();
         float snapBackThreshold = getSnapBackThreshold();
         float targetRight = getDismissThreshold();
@@ -826,10 +802,6 @@
     }
 
     public static class NotificationMenuItem implements MenuItem {
-
-        // Constant signaling that this MenuItem should not appear in slow swipe.
-        public static final int OMIT_FROM_SWIPE_MENU = -1;
-
         View mMenuView;
         GutsContent mGutsContent;
         String mContentDescription;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
index 769f0b5..01ee788 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
@@ -80,7 +80,6 @@
                 assistantFeedbackController, metricsLogger, onCloseClick);
 
         mNotificationManager = iNotificationManager;
-
         mPackageDemotionInteractor = packageDemotionInteractor;
 
         bindDemote(entry.getSbn(), pkg);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedPermissionGutsContent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedPermissionGutsContent.java
deleted file mode 100644
index 222a1f4..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedPermissionGutsContent.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.service.notification.StatusBarNotification;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
-
-/**
- * This GutsContent shows an explanatory interstitial telling the user they've just revoked this
- * app's permission to post Promoted/Live notifications.
- * If the guts are dismissed without further action, the revocation is committed.
- * If the user hits undo, the permission is not revoked.
- */
-public class PromotedPermissionGutsContent extends LinearLayout
-        implements NotificationGuts.GutsContent, View.OnClickListener {
-
-    private static final String TAG = "SnoozyPromotedGuts";
-
-    private NotificationGuts mGutsContainer;
-    private StatusBarNotification mSbn;
-
-    private TextView mUndoButton;
-
-    private MetricsLogger mMetricsLogger = new MetricsLogger();
-    private OnClickListener mDemoteAction;
-
-    public PromotedPermissionGutsContent(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mUndoButton = (TextView) findViewById(R.id.undo);
-        mUndoButton.setOnClickListener(this);
-        mUndoButton.setContentDescription(
-                getContext().getString(R.string.snooze_undo_content_description));
-
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        dispatchConfigurationChanged(getResources().getConfiguration());
-    }
-
-    /**
-     * Update the content description of the snooze view based on the snooze option and whether the
-     * snooze options are expanded or not.
-     * For example, this will be something like "Collapsed\u2029Snooze for 1 hour". The paragraph
-     * separator is added to introduce a break in speech, to match what TalkBack does by default
-     * when you e.g. press on a notification.
-     */
-    private void updateContentDescription() {
-        //
-    }
-
-
-    @Override
-    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
-        if (super.performAccessibilityActionInternal(action, arguments)) {
-            return true;
-        }
-        if (action == R.id.action_snooze_undo) {
-            undoDemote(mUndoButton);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * TODO docs
-     * @param sbn
-     */
-    public void setStatusBarNotification(StatusBarNotification sbn) {
-        mSbn = sbn;
-        TextView demoteExplanation = (TextView) findViewById(R.id.demote_explain);
-        demoteExplanation.setText(mContext.getResources().getString(R.string.demote_explain_text,
-                mSbn.getPackageName()));
-    }
-
-    @Override
-    public void onClick(View v) {
-        if (mGutsContainer != null) {
-            mGutsContainer.resetFalsingCheck();
-        }
-        final int id = v.getId();
-        if (id == R.id.undo) {
-            undoDemote(v);
-        }
-
-    }
-
-    private void undoDemote(View v) {
-        // Don't commit the demote action, instead log the undo and dismiss the view.
-        mGutsContainer.closeControls(v, /* save= */ false);
-    }
-
-    @Override
-    public int getActualHeight() {
-        return getHeight();
-    }
-
-    @Override
-    public boolean willBeRemoved() {
-        return false;
-    }
-
-    @Override
-    public View getContentView() {
-        return this;
-    }
-
-    @Override
-    public void setGutsParent(NotificationGuts guts) {
-        mGutsContainer = guts;
-    }
-
-    @Override
-    public boolean handleCloseControls(boolean save, boolean force) {
-        if (!save) {
-            // Undo changes and let the guts handle closing the view
-            return false;
-        } else {
-            // Commit demote action.
-            mDemoteAction.onClick(this);
-            return false;
-        }
-    }
-
-    @Override
-    public boolean isLeavebehind() {
-        return true;
-    }
-
-    @Override
-    public boolean shouldBeSavedOnClose() {
-        return true;
-    }
-
-    @Override
-    public boolean needsFalsingProtection() {
-        return false;
-    }
-
-    public void setOnDemoteAction(OnClickListener demoteAction) {
-        mDemoteAction = demoteAction;
-    }
-
-}
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 503256a..9fea750 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
@@ -3220,7 +3220,8 @@
         updateAnimationState(child);
         updateChronometerForChild(child);
         if (child instanceof ExpandableNotificationRow row) {
-            row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX, /* force= */ true);
+            row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX);
+
         }
     }
 
@@ -6156,7 +6157,7 @@
                 View child = getChildAt(i);
                 if (child instanceof ExpandableNotificationRow) {
                     ((ExpandableNotificationRow) child).setDismissUsingRowTranslationX(
-                            dismissUsingRowTranslationX, /* force= */ false);
+                            dismissUsingRowTranslationX);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt
index 1a8ca95..f4afc24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.kairos.BuildSpec
 import com.android.systemui.kairos.ExperimentalKairosApi
 import com.android.systemui.kairos.State
+import com.android.systemui.kairos.combine
 import com.android.systemui.kairos.flatMap
 import com.android.systemui.kairosBuilder
 import com.android.systemui.log.table.TableLogBuffer
@@ -55,9 +56,15 @@
     @Assisted private val isCarrierMerged: State<Boolean>,
 ) : MobileConnectionRepositoryKairos, KairosBuilder by kairosBuilder() {
 
+    private var dumpCache: DumpCache? = null
+
     init {
         onActivated {
             logDiffsForTable(isCarrierMerged, tableLogBuffer, columnName = "isCarrierMerged")
+            combine(isCarrierMerged, activeRepo) { isCarrierMerged, activeRepo ->
+                    DumpCache(isCarrierMerged, activeRepo)
+                }
+                .observe { dumpCache = it }
         }
     }
 
@@ -198,13 +205,6 @@
 
     override val isInEcmMode: State<Boolean> = activeRepo.flatMap { it.isInEcmMode }
 
-    private var dumpCache: DumpCache? = null
-
-    private data class DumpCache(
-        val isCarrierMerged: Boolean,
-        val activeRepo: MobileConnectionRepositoryKairos,
-    )
-
     fun dump(pw: PrintWriter) {
         val cache = dumpCache ?: return
         val ipw = IndentingPrintWriter(pw, "  ")
@@ -227,6 +227,11 @@
         ipw.decreaseIndent()
     }
 
+    private data class DumpCache(
+        val isCarrierMerged: Boolean,
+        val activeRepo: MobileConnectionRepositoryKairos,
+    )
+
     @AssistedFactory
     interface Factory {
         fun create(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt
index e468159..e6c2921 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt
@@ -131,6 +131,8 @@
     private val mobileRepoFactory: Lazy<ConnectionRepoFactory>,
 ) : MobileConnectionsRepositoryKairos, Dumpable, KairosBuilder by kairosBuilder() {
 
+    private var dumpCache: DumpCache? = null
+
     init {
         dumpManager.registerNormalDumpable("MobileConnectionsRepositoryKairos", this)
     }
@@ -253,6 +255,7 @@
                 .asIncremental()
                 .mapValues { (subId, sub) -> mobileRepoFactory.get().create(subId) }
                 .applyLatestSpecForKey()
+                .apply { observe { dumpCache = DumpCache(it) } }
         }
 
     private val telephonyManagerState: State<Pair<Int?, Set<Int>>> = buildState {
@@ -479,10 +482,6 @@
             profileClass = profileClass,
         )
 
-    private var dumpCache: DumpCache? = null
-
-    private data class DumpCache(val repos: Map<Int, FullMobileConnectionRepositoryKairos>)
-
     override fun dump(pw: PrintWriter, args: Array<String>) {
         val cache = dumpCache ?: return
         val ipw = IndentingPrintWriter(pw, " ")
@@ -494,10 +493,16 @@
 
         ipw.println("Connections (${cache.repos.size} total):")
         ipw.increaseIndent()
-        cache.repos.values.forEach { it.dump(ipw) }
+        cache.repos.values.forEach {
+            if (it is FullMobileConnectionRepositoryKairos) {
+                it.dump(ipw)
+            }
+        }
         ipw.decreaseIndent()
     }
 
+    private data class DumpCache(val repos: Map<Int, MobileConnectionRepositoryKairos>)
+
     fun interface ConnectionRepoFactory {
         fun create(subId: Int): BuildSpec<MobileConnectionRepositoryKairos>
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 0eabb4ec..af4e61a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -61,31 +61,31 @@
      * consider this connection to be serving data, and thus want to show a network type icon, when
      * data is connected. Other data connection states would typically cause us not to show the icon
      */
-    val isDataConnected: StateFlow<Boolean>
+    val isDataConnected: Flow<Boolean>
 
     /** True if we consider this connection to be in service, i.e. can make calls */
-    val isInService: StateFlow<Boolean>
+    val isInService: Flow<Boolean>
 
     /** True if this connection is emergency only */
-    val isEmergencyOnly: StateFlow<Boolean>
+    val isEmergencyOnly: Flow<Boolean>
 
     /** Observable for the data enabled state of this connection */
-    val isDataEnabled: StateFlow<Boolean>
+    val isDataEnabled: Flow<Boolean>
 
     /** True if the RAT icon should always be displayed and false otherwise. */
-    val alwaysShowDataRatIcon: StateFlow<Boolean>
+    val alwaysShowDataRatIcon: Flow<Boolean>
 
     /** Canonical representation of the current mobile signal strength as a triangle. */
-    val signalLevelIcon: StateFlow<SignalIconModel>
+    val signalLevelIcon: Flow<SignalIconModel>
 
     /** Observable for RAT type (network type) indicator */
-    val networkTypeIconGroup: StateFlow<NetworkTypeIconModel>
+    val networkTypeIconGroup: Flow<NetworkTypeIconModel>
 
     /** Whether or not to show the slice attribution */
-    val showSliceAttribution: StateFlow<Boolean>
+    val showSliceAttribution: Flow<Boolean>
 
     /** True if this connection is satellite-based */
-    val isNonTerrestrial: StateFlow<Boolean>
+    val isNonTerrestrial: Flow<Boolean>
 
     /**
      * Provider name for this network connection. The name can be one of 3 values:
@@ -95,7 +95,7 @@
      *    override in [connectionInfo.operatorAlphaShort], a value that is derived from
      *    [ServiceState]
      */
-    val networkName: StateFlow<NetworkNameModel>
+    val networkName: Flow<NetworkNameModel>
 
     /**
      * Provider name for this network connection. The name can be one of 3 values:
@@ -108,26 +108,26 @@
      * TODO(b/296600321): De-duplicate this field with [networkName] after determining the data
      *   provided is identical
      */
-    val carrierName: StateFlow<String>
+    val carrierName: Flow<String>
 
     /** True if there is only one active subscription. */
-    val isSingleCarrier: StateFlow<Boolean>
+    val isSingleCarrier: Flow<Boolean>
 
     /**
      * True if this connection is considered roaming. The roaming bit can come from [ServiceState],
      * or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a
      * connection to be roaming while carrier network change is active
      */
-    val isRoaming: StateFlow<Boolean>
+    val isRoaming: Flow<Boolean>
 
     /** See [MobileIconsInteractor.isForceHidden]. */
     val isForceHidden: Flow<Boolean>
 
     /** See [MobileConnectionRepository.isAllowedDuringAirplaneMode]. */
-    val isAllowedDuringAirplaneMode: StateFlow<Boolean>
+    val isAllowedDuringAirplaneMode: Flow<Boolean>
 
     /** True when in carrier network change mode */
-    val carrierNetworkChangeActive: StateFlow<Boolean>
+    val carrierNetworkChangeActive: Flow<Boolean>
 }
 
 /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt
index 87877b3..6b9c537 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt
@@ -32,11 +32,20 @@
 import com.android.systemui.kairos.mapValues
 import com.android.systemui.kairos.toColdConflatedFlow
 import com.android.systemui.kairosBuilder
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import dagger.Provides
 import dagger.multibindings.ElementsIntoSet
 import javax.inject.Inject
@@ -45,6 +54,8 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 @ExperimentalKairosApi
@@ -60,6 +71,7 @@
     context: Context,
     mobileMappingsProxy: MobileMappingsProxy,
     private val userSetupRepo: UserSetupRepository,
+    private val logFactory: TableLogBufferFactory,
 ) : MobileIconsInteractor, KairosBuilder by kairosBuilder() {
 
     private val interactorsBySubIdK = buildIncremental {
@@ -158,7 +170,37 @@
         get() = repo.isDeviceEmergencyCallCapable
 
     override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
-        interactorsBySubId.value[subId] ?: error("Unknown subscription id: $subId")
+        object : MobileIconInteractor {
+            override val tableLogBuffer: TableLogBuffer =
+                logFactory.getOrCreate(tableBufferLogName(subId), MOBILE_CONNECTION_BUFFER_SIZE)
+            override val activity: Flow<DataActivityModel> = latest { activity }
+            override val mobileIsDefault: Flow<Boolean> = latest { mobileIsDefault }
+            override val isDataConnected: Flow<Boolean> = latest { isDataConnected }
+            override val isInService: Flow<Boolean> = latest { isInService }
+            override val isEmergencyOnly: Flow<Boolean> = latest { isEmergencyOnly }
+            override val isDataEnabled: Flow<Boolean> = latest { isDataEnabled }
+            override val alwaysShowDataRatIcon: Flow<Boolean> = latest { alwaysShowDataRatIcon }
+            override val signalLevelIcon: Flow<SignalIconModel> = latest { signalLevelIcon }
+            override val networkTypeIconGroup: Flow<NetworkTypeIconModel> = latest {
+                networkTypeIconGroup
+            }
+            override val showSliceAttribution: Flow<Boolean> = latest { showSliceAttribution }
+            override val isNonTerrestrial: Flow<Boolean> = latest { isNonTerrestrial }
+            override val networkName: Flow<NetworkNameModel> = latest { networkName }
+            override val carrierName: Flow<String> = latest { carrierName }
+            override val isSingleCarrier: Flow<Boolean> = latest { isSingleCarrier }
+            override val isRoaming: Flow<Boolean> = latest { isRoaming }
+            override val isForceHidden: Flow<Boolean> = latest { isForceHidden }
+            override val isAllowedDuringAirplaneMode: Flow<Boolean> = latest {
+                isAllowedDuringAirplaneMode
+            }
+            override val carrierNetworkChangeActive: Flow<Boolean> = latest {
+                carrierNetworkChangeActive
+            }
+
+            private fun <T> latest(block: MobileIconInteractor.() -> Flow<T>): Flow<T> =
+                interactorsBySubId.flatMapLatestConflated { it[subId]?.block() ?: emptyFlow() }
+        }
 
     @dagger.Module
     object Module {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 9a81992..7f778bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -256,6 +256,9 @@
                     if (mClockFormat != null) {
                         mClockFormat.setTimeZone(mCalendar.getTimeZone());
                     }
+                    if (mContentDescriptionFormat != null) {
+                        mContentDescriptionFormat.setTimeZone(mCalendar.getTimeZone());
+                    }
                 });
             } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
                 final Locale newLocale = getResources().getConfiguration().locale;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index bc7ab9d..2ea4e7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -582,7 +582,7 @@
     public void testIconScrollXAfterTranslationAndReset() throws Exception {
         ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
 
-        group.setDismissUsingRowTranslationX(false, false);
+        group.setDismissUsingRowTranslationX(false);
         group.setTranslation(50);
         assertEquals(50, -group.getEntry().getIcons().getShelfIcon().getScrollX());
 
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
index b956e44..90a9271 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
@@ -281,7 +281,6 @@
                             },
                     )
                 }
-                downstreamSet.clear()
             }
         }
         reset()
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
index c11eb12..81f3702 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
@@ -145,7 +145,14 @@
                 val conn = branchNode.upstream
                 severed.add(conn)
                 conn.removeDownstream(downstream = branchNode.schedulable)
-                depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                if (conn.depthTracker.snapshotIsDirect) {
+                    depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                } else {
+                    depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth)
+                    depthTracker.updateIndirectRoots(
+                        removals = conn.depthTracker.snapshotIndirectRoots
+                    )
+                }
             }
         }
 
@@ -156,7 +163,14 @@
                 val conn = branchNode.upstream
                 severed.add(conn)
                 conn.removeDownstream(downstream = branchNode.schedulable)
-                depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                if (conn.depthTracker.snapshotIsDirect) {
+                    depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                } else {
+                    depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth)
+                    depthTracker.updateIndirectRoots(
+                        removals = conn.depthTracker.snapshotIndirectRoots
+                    )
+                }
             }
 
             // add new
@@ -343,13 +357,8 @@
                 val (patchesConn, needsEval) =
                     getPatches(evalScope).activate(evalScope, downstream = muxNode.schedulable)
                         ?: run {
-                            // Turns out we can't connect to patches, so update our depth and
-                            // propagate
-                            if (muxNode.depthTracker.setIsIndirectRoot(false)) {
-                                // TODO: schedules might not be necessary now that we're not
-                                // parallel?
-                                muxNode.depthTracker.schedule(evalScope.scheduler, muxNode)
-                            }
+                            // Turns out we can't connect to patches, so update our depth
+                            muxNode.depthTracker.setIsIndirectRoot(false)
                             return
                         }
                 muxNode.patches = patchesConn
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
index cb2c6e5..faef6a2 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
@@ -109,7 +109,14 @@
                 val conn: NodeConnection<V> = branchNode.upstream
                 severed.add(conn)
                 conn.removeDownstream(downstream = branchNode.schedulable)
-                depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                if (conn.depthTracker.snapshotIsDirect) {
+                    depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                } else {
+                    depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth)
+                    depthTracker.updateIndirectRoots(
+                        removals = conn.depthTracker.snapshotIndirectRoots
+                    )
+                }
             }
         }
 
@@ -123,7 +130,14 @@
                 val conn: NodeConnection<V> = oldBranch.upstream
                 severed.add(conn)
                 conn.removeDownstream(downstream = oldBranch.schedulable)
-                depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                if (conn.depthTracker.snapshotIsDirect) {
+                    depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                } else {
+                    depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth)
+                    depthTracker.updateIndirectRoots(
+                        removals = conn.depthTracker.snapshotIndirectRoots
+                    )
+                }
             }
 
             // add new
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index 6db62c8..ccb9e3e 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -301,7 +301,8 @@
             throw new IllegalArgumentException(
                     "Unknown session ID in closeSession: id=" + sessionId);
         }
-        halCloseEndpointSession(sessionId, ContextHubServiceUtil.toHalReason(reason));
+        mEndpointManager.halCloseEndpointSession(
+                sessionId, ContextHubServiceUtil.toHalReason(reason));
     }
 
     @Override
@@ -312,7 +313,7 @@
             // Iterate in reverse since cleanupSessionResources will remove the entry
             for (int i = mSessionMap.size() - 1; i >= 0; i--) {
                 int id = mSessionMap.keyAt(i);
-                halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE);
+                mEndpointManager.halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE);
                 cleanupSessionResources(id);
             }
         }
@@ -444,7 +445,8 @@
                     int id = mSessionMap.keyAt(i);
                     HubEndpointInfo target = mSessionMap.get(id).getRemoteEndpointInfo();
                     if (!hasEndpointPermissions(target)) {
-                        halCloseEndpointSessionNoThrow(id, Reason.PERMISSION_DENIED);
+                        mEndpointManager.halCloseEndpointSessionNoThrow(
+                                id, Reason.PERMISSION_DENIED);
                         onCloseEndpointSession(id, Reason.PERMISSION_DENIED);
                         // Resource cleanup is done in onCloseEndpointSession
                     }
@@ -503,17 +505,7 @@
         mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */);
     }
 
-    /* package */ void onEndpointSessionOpenRequest(
-            int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
-        Optional<Byte> error =
-                onEndpointSessionOpenRequestInternal(sessionId, initiator, serviceDescriptor);
-        if (error.isPresent()) {
-            halCloseEndpointSessionNoThrow(sessionId, error.get());
-            onCloseEndpointSession(sessionId, error.get());
-            // Resource cleanup is done in onCloseEndpointSession
-        }
-    }
-
+    /** Handle close endpoint callback to the client side */
     /* package */ void onCloseEndpointSession(int sessionId, byte reason) {
         if (!cleanupSessionResources(sessionId)) {
             Log.w(TAG, "Unknown session ID in onCloseEndpointSession: id=" + sessionId);
@@ -585,7 +577,7 @@
         }
     }
 
-    private Optional<Byte> onEndpointSessionOpenRequestInternal(
+    /* package */ Optional<Byte> onEndpointSessionOpenRequest(
             int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
         if (!hasEndpointPermissions(initiator)) {
             Log.e(
@@ -594,15 +586,41 @@
                             + initiator
                             + " doesn't have permission for "
                             + mEndpointInfo);
-            return Optional.of(Reason.PERMISSION_DENIED);
+            byte reason = Reason.PERMISSION_DENIED;
+            onCloseEndpointSession(sessionId, reason);
+            return Optional.of(reason);
         }
 
+        // Check & handle error cases for duplicated session id.
+        final boolean existingSession;
+        final boolean existingSessionActive;
         synchronized (mOpenSessionLock) {
             if (hasSessionId(sessionId)) {
-                Log.e(TAG, "Existing session in onEndpointSessionOpenRequest: id=" + sessionId);
-                return Optional.of(Reason.UNSPECIFIED);
+                existingSession = true;
+                existingSessionActive = mSessionMap.get(sessionId).isActive();
+                Log.w(
+                        TAG,
+                        "onEndpointSessionOpenRequest: "
+                                + "Existing session ID: "
+                                + sessionId
+                                + ", isActive: "
+                                + existingSessionActive);
+            } else {
+                existingSession = false;
+                existingSessionActive = false;
+                mSessionMap.put(sessionId, new Session(initiator, true));
             }
-            mSessionMap.put(sessionId, new Session(initiator, true));
+        }
+
+        if (existingSession) {
+            if (existingSessionActive) {
+                // Existing session is already active, call onSessionOpenComplete.
+                openSessionRequestComplete(sessionId);
+                return Optional.empty();
+            }
+            // Reject the session open request for now. Consider invalidating previous pending
+            // session open request based on timeout.
+            return Optional.of(Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
         }
 
         boolean success =
@@ -610,7 +628,11 @@
                         (consumer) ->
                                 consumer.onSessionOpenRequest(
                                         sessionId, initiator, serviceDescriptor));
-        return success ? Optional.empty() : Optional.of(Reason.UNSPECIFIED);
+        byte reason = Reason.UNSPECIFIED;
+        if (!success) {
+            onCloseEndpointSession(sessionId, reason);
+        }
+        return success ? Optional.empty() : Optional.of(reason);
     }
 
     private byte onMessageReceivedInternal(int sessionId, HubMessage message) {
@@ -657,29 +679,6 @@
     }
 
     /**
-     * Calls the HAL closeEndpointSession API.
-     *
-     * @param sessionId The session ID to close
-     * @param halReason The HAL reason
-     */
-    private void halCloseEndpointSession(int sessionId, byte halReason) throws RemoteException {
-        try {
-            mHubInterface.closeEndpointSession(sessionId, halReason);
-        } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
-            throw e;
-        }
-    }
-
-    /** Same as halCloseEndpointSession but does not throw the exception */
-    private void halCloseEndpointSessionNoThrow(int sessionId, byte halReason) {
-        try {
-            halCloseEndpointSession(sessionId, halReason);
-        } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
-            Log.e(TAG, "Exception while calling HAL closeEndpointSession", e);
-        }
-    }
-
-    /**
      * Cleans up resources related to a session with the provided ID.
      *
      * @param sessionId The session ID to clean up resources for
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
index 8ab581e..e156159 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
@@ -29,6 +29,7 @@
 import android.hardware.contexthub.IContextHubEndpointCallback;
 import android.hardware.contexthub.IEndpointCommunication;
 import android.hardware.contexthub.MessageDeliveryStatus;
+import android.hardware.contexthub.Reason;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Log;
@@ -42,6 +43,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
@@ -316,6 +318,11 @@
         }
     }
 
+    /** Returns if a sessionId can be allocated for the service hub. */
+    private boolean isSessionIdAllocatedForService(int sessionId) {
+        return sessionId > mMaxSessionId || sessionId < mMinSessionId;
+    }
+
     /**
      * Unregisters an endpoint given its ID.
      *
@@ -337,8 +344,7 @@
         }
     }
 
-    @Override
-    public void onEndpointSessionOpenRequest(
+    private Optional<Byte> onEndpointSessionOpenRequestInternal(
             int sessionId,
             HubEndpointInfo.HubEndpointIdentifier destination,
             HubEndpointInfo.HubEndpointIdentifier initiator,
@@ -348,7 +354,7 @@
                     TAG,
                     "onEndpointSessionOpenRequest: invalid destination hub ID: "
                             + destination.getHub());
-            return;
+            return Optional.of(Reason.ENDPOINT_INVALID);
         }
         ContextHubEndpointBroker broker = mEndpointMap.get(destination.getEndpoint());
         if (broker == null) {
@@ -356,7 +362,7 @@
                     TAG,
                     "onEndpointSessionOpenRequest: unknown destination endpoint ID: "
                             + destination.getEndpoint());
-            return;
+            return Optional.of(Reason.ENDPOINT_INVALID);
         }
         HubEndpointInfo initiatorInfo = mHubInfoRegistry.getEndpointInfo(initiator);
         if (initiatorInfo == null) {
@@ -364,9 +370,29 @@
                     TAG,
                     "onEndpointSessionOpenRequest: unknown initiator endpoint ID: "
                             + initiator.getEndpoint());
-            return;
+            return Optional.of(Reason.ENDPOINT_INVALID);
         }
-        broker.onEndpointSessionOpenRequest(sessionId, initiatorInfo, serviceDescriptor);
+        if (!isSessionIdAllocatedForService(sessionId)) {
+            Log.e(
+                    TAG,
+                    "onEndpointSessionOpenRequest: invalid session ID, rejected:"
+                            + " sessionId="
+                            + sessionId);
+            return Optional.of(Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
+        }
+        return broker.onEndpointSessionOpenRequest(sessionId, initiatorInfo, serviceDescriptor);
+    }
+
+    @Override
+    public void onEndpointSessionOpenRequest(
+            int sessionId,
+            HubEndpointInfo.HubEndpointIdentifier destination,
+            HubEndpointInfo.HubEndpointIdentifier initiator,
+            String serviceDescriptor) {
+        Optional<Byte> errorOptional =
+                onEndpointSessionOpenRequestInternal(
+                        sessionId, destination, initiator, serviceDescriptor);
+        errorOptional.ifPresent((error) -> halCloseEndpointSessionNoThrow(sessionId, error));
     }
 
     @Override
@@ -418,6 +444,30 @@
         }
     }
 
+    /**
+     * Calls the HAL closeEndpointSession API.
+     *
+     * @param sessionId The session ID to close
+     * @param halReason The HAL reason
+     */
+    /* package */ void halCloseEndpointSession(int sessionId, byte halReason)
+            throws RemoteException {
+        try {
+            mHubInterface.closeEndpointSession(sessionId, halReason);
+        } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
+            throw e;
+        }
+    }
+
+    /** Same as halCloseEndpointSession but does not throw the exception */
+    /* package */ void halCloseEndpointSessionNoThrow(int sessionId, byte halReason) {
+        try {
+            halCloseEndpointSession(sessionId, halReason);
+        } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
+            Log.e(TAG, "Exception while calling HAL closeEndpointSession", e);
+        }
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 543e32f..9ff6eb6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -34,6 +34,7 @@
 import android.app.admin.PolicyKey;
 import android.app.admin.PolicyValue;
 import android.app.admin.UserRestrictionPolicyKey;
+import android.app.admin.flags.Flags;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentFilter;
@@ -282,7 +283,9 @@
 
     static PolicyDefinition<Set<String>> PERMITTED_INPUT_METHODS = new PolicyDefinition<>(
             new NoArgsPolicyKey(DevicePolicyIdentifiers.PERMITTED_INPUT_METHODS_POLICY),
-            new MostRecent<>(),
+            (Flags.usePolicyIntersectionForPermittedInputMethods()
+                ? new StringSetIntersection()
+                : new MostRecent<>()),
             POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE,
             PolicyEnforcerCallbacks::noOp,
             new PackageSetPolicySerializer());
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java
new file mode 100644
index 0000000..bc075b02
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.annotation.NonNull;
+import android.app.admin.PolicyValue;
+import android.app.admin.PackageSetPolicyValue;
+
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Objects;
+import java.util.Set;
+
+final class StringSetIntersection extends ResolutionMechanism<Set<String>> {
+
+    @Override
+    PolicyValue<Set<String>> resolve(
+            @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<Set<String>>> adminPolicies) {
+        Objects.requireNonNull(adminPolicies);
+        Set<String> intersectionOfPolicies = null;
+        for (PolicyValue<Set<String>> policy : adminPolicies.values()) {
+            if (intersectionOfPolicies == null) {
+                intersectionOfPolicies = new HashSet<>(policy.getValue());
+            } else {
+                intersectionOfPolicies.retainAll(policy.getValue());
+            }
+        }
+        if (intersectionOfPolicies == null) {
+            return null;
+        }
+        // Note that the resulting set below may be empty, but that's fine:
+        // particular policy should decide what is the meaning of an empty set.
+        return new PackageSetPolicyValue(intersectionOfPolicies);
+    }
+
+    @Override
+    android.app.admin.StringSetIntersection getParcelableResolutionMechanism() {
+        return new android.app.admin.StringSetIntersection();
+    }
+
+    @Override
+    public String toString() {
+        return "StringSetIntersection {}";
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
index 4d2dcf6..43b1ec3 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -67,12 +68,15 @@
     private static final int SESSION_ID_RANGE = ContextHubEndpointManager.SERVICE_SESSION_RANGE;
     private static final int MIN_SESSION_ID = 0;
     private static final int MAX_SESSION_ID = MIN_SESSION_ID + SESSION_ID_RANGE - 1;
+    private static final int SESSION_ID_FOR_OPEN_REQUEST = MAX_SESSION_ID + 1;
+    private static final int INVALID_SESSION_ID_FOR_OPEN_REQUEST = MIN_SESSION_ID + 1;
 
     private static final String ENDPOINT_NAME = "Example test endpoint";
     private static final int ENDPOINT_ID = 1;
     private static final String ENDPOINT_PACKAGE_NAME = "com.android.server.location.contexthub";
 
     private static final String TARGET_ENDPOINT_NAME = "Example target endpoint";
+    private static final String ENDPOINT_SERVICE_DESCRIPTOR = "serviceDescriptor";
     private static final int TARGET_ENDPOINT_ID = 1;
 
     private static final int SAMPLE_MESSAGE_TYPE = 1234;
@@ -225,6 +229,105 @@
     }
 
     @Test
+    public void testEndpointSessionOpenRequest() throws RemoteException {
+        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+        IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+        HubEndpointInfo targetInfo =
+                new HubEndpointInfo(
+                        TARGET_ENDPOINT_NAME,
+                        TARGET_ENDPOINT_ID,
+                        ENDPOINT_PACKAGE_NAME,
+                        Collections.emptyList());
+        mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo});
+        mEndpointManager.onEndpointSessionOpenRequest(
+                SESSION_ID_FOR_OPEN_REQUEST,
+                endpoint.getAssignedHubEndpointInfo().getIdentifier(),
+                targetInfo.getIdentifier(),
+                ENDPOINT_SERVICE_DESCRIPTOR);
+
+        verify(mMockCallback)
+                .onSessionOpenRequest(
+                        SESSION_ID_FOR_OPEN_REQUEST, targetInfo, ENDPOINT_SERVICE_DESCRIPTOR);
+
+        // Accept
+        endpoint.openSessionRequestComplete(SESSION_ID_FOR_OPEN_REQUEST);
+        verify(mMockEndpointCommunications)
+                .endpointSessionOpenComplete(SESSION_ID_FOR_OPEN_REQUEST);
+
+        unregisterExampleEndpoint(endpoint);
+    }
+
+    @Test
+    public void testEndpointSessionOpenRequestWithInvalidSessionId() throws RemoteException {
+        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+        IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+        HubEndpointInfo targetInfo =
+                new HubEndpointInfo(
+                        TARGET_ENDPOINT_NAME,
+                        TARGET_ENDPOINT_ID,
+                        ENDPOINT_PACKAGE_NAME,
+                        Collections.emptyList());
+        mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo});
+        mEndpointManager.onEndpointSessionOpenRequest(
+                INVALID_SESSION_ID_FOR_OPEN_REQUEST,
+                endpoint.getAssignedHubEndpointInfo().getIdentifier(),
+                targetInfo.getIdentifier(),
+                ENDPOINT_SERVICE_DESCRIPTOR);
+        verify(mMockEndpointCommunications)
+                .closeEndpointSession(
+                        INVALID_SESSION_ID_FOR_OPEN_REQUEST,
+                        Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
+        verify(mMockCallback, never())
+                .onSessionOpenRequest(
+                        INVALID_SESSION_ID_FOR_OPEN_REQUEST,
+                        targetInfo,
+                        ENDPOINT_SERVICE_DESCRIPTOR);
+
+        unregisterExampleEndpoint(endpoint);
+    }
+
+    @Test
+    public void testEndpointSessionOpenRequest_duplicatedSessionId_noopWhenSessionIsActive()
+            throws RemoteException {
+        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+        IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+        HubEndpointInfo targetInfo =
+                new HubEndpointInfo(
+                        TARGET_ENDPOINT_NAME,
+                        TARGET_ENDPOINT_ID,
+                        ENDPOINT_PACKAGE_NAME,
+                        Collections.emptyList());
+        mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo});
+        mEndpointManager.onEndpointSessionOpenRequest(
+                SESSION_ID_FOR_OPEN_REQUEST,
+                endpoint.getAssignedHubEndpointInfo().getIdentifier(),
+                targetInfo.getIdentifier(),
+                ENDPOINT_SERVICE_DESCRIPTOR);
+        endpoint.openSessionRequestComplete(SESSION_ID_FOR_OPEN_REQUEST);
+        // Now session with id SESSION_ID_FOR_OPEN_REQUEST is active
+
+        // Duplicated session open request
+        mEndpointManager.onEndpointSessionOpenRequest(
+                SESSION_ID_FOR_OPEN_REQUEST,
+                endpoint.getAssignedHubEndpointInfo().getIdentifier(),
+                targetInfo.getIdentifier(),
+                ENDPOINT_SERVICE_DESCRIPTOR);
+
+        // Client API is only invoked once
+        verify(mMockCallback, times(1))
+                .onSessionOpenRequest(
+                        SESSION_ID_FOR_OPEN_REQUEST, targetInfo, ENDPOINT_SERVICE_DESCRIPTOR);
+        // HAL still receives two open complete notifications
+        verify(mMockEndpointCommunications, times(2))
+                .endpointSessionOpenComplete(SESSION_ID_FOR_OPEN_REQUEST);
+
+        unregisterExampleEndpoint(endpoint);
+    }
+
+    @Test
     public void testMessageTransaction() throws RemoteException {
         IContextHubEndpoint endpoint = registerExampleEndpoint();
         testMessageTransactionInternal(endpoint, /* deliverMessageStatus= */ true);