Merge "Fix relaunch of freeform tasks outside desktop" into main
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
index a3beaf4..209f323 100644
--- a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
@@ -216,7 +216,11 @@
                 oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirection();
 
         if (densityChange || dirChange) {
-            mImeSwitcherIcon = getDrawable(com.android.internal.R.drawable.ic_ime_switcher);
+            final int switcherResId = Flags.imeSwitcherRevamp()
+                    ? com.android.internal.R.drawable.ic_ime_switcher_new
+                    : com.android.internal.R.drawable.ic_ime_switcher;
+
+            mImeSwitcherIcon = getDrawable(switcherResId);
         }
         if (orientationChange || densityChange || dirChange) {
             mBackIcon = getBackDrawable();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 82a7e16..a23e383 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -30,6 +30,7 @@
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
+import static android.view.accessibility.Flags.removeChildHoverCheckForTouchExploration;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
@@ -17486,9 +17487,9 @@
      * Dispatching hover events to {@link TouchDelegate} to improve accessibility.
      * <p>
      * This method is dispatching hover events to the delegate target to support explore by touch.
-     * Similar to {@link ViewGroup#dispatchTouchEvent}, this method send proper hover events to
+     * Similar to {@link ViewGroup#dispatchTouchEvent}, this method sends proper hover events to
      * the delegate target according to the pointer and the touch area of the delegate while touch
-     * exploration enabled.
+     * exploration is enabled.
      * </p>
      *
      * @param event The motion event dispatch to the delegate target.
@@ -17520,17 +17521,33 @@
         // hover events but receive accessibility focus, it should also not delegate to these
         // views when hovered.
         if (!oldHoveringTouchDelegate) {
-            if ((action == MotionEvent.ACTION_HOVER_ENTER
-                    || action == MotionEvent.ACTION_HOVER_MOVE)
-                    && !pointInHoveredChild(event)
-                    && pointInDelegateRegion) {
-                mHoveringTouchDelegate = true;
+            if (removeChildHoverCheckForTouchExploration()) {
+                if ((action == MotionEvent.ACTION_HOVER_ENTER
+                        || action == MotionEvent.ACTION_HOVER_MOVE) && pointInDelegateRegion) {
+                    mHoveringTouchDelegate = true;
+                }
+            } else {
+                if ((action == MotionEvent.ACTION_HOVER_ENTER
+                        || action == MotionEvent.ACTION_HOVER_MOVE)
+                        && !pointInHoveredChild(event)
+                        && pointInDelegateRegion) {
+                    mHoveringTouchDelegate = true;
+                }
             }
         } else {
-            if (action == MotionEvent.ACTION_HOVER_EXIT
-                    || (action == MotionEvent.ACTION_HOVER_MOVE
+            if (removeChildHoverCheckForTouchExploration()) {
+                if (action == MotionEvent.ACTION_HOVER_EXIT
+                        || (action == MotionEvent.ACTION_HOVER_MOVE)) {
+                    if (!pointInDelegateRegion) {
+                        mHoveringTouchDelegate = false;
+                    }
+                }
+            } else {
+                if (action == MotionEvent.ACTION_HOVER_EXIT
+                        || (action == MotionEvent.ACTION_HOVER_MOVE
                         && (pointInHoveredChild(event) || !pointInDelegateRegion))) {
-                mHoveringTouchDelegate = false;
+                    mHoveringTouchDelegate = false;
+                }
             }
         }
         switch (action) {
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index d0bc57b..44c1acc 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -128,6 +128,13 @@
 }
 
 flag {
+    namespace: "accessibility"
+    name: "remove_child_hover_check_for_touch_exploration"
+    description: "Remove a check for a hovered child that prevents touch events from being delegated to non-direct descendants"
+    bug: "304770837"
+}
+
+flag {
     name: "skip_accessibility_warning_dialog_for_trusted_services"
     namespace: "accessibility"
     description: "Skips showing the accessibility warning dialog for trusted services."
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 098f655..0e66f7a 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -891,12 +891,13 @@
     @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API)
     @Nullable
     public Intent createImeLanguageSettingsActivityIntent() {
-        if (TextUtils.isEmpty(mLanguageSettingsActivityName)) {
+        final var activityName = !TextUtils.isEmpty(mLanguageSettingsActivityName)
+                ? mLanguageSettingsActivityName : mSettingsActivityName;
+        if (TextUtils.isEmpty(activityName)) {
             return null;
         }
         return new Intent(ACTION_IME_LANGUAGE_SETTINGS).setComponent(
-                new ComponentName(getServiceInfo().packageName,
-                        mLanguageSettingsActivityName)
+                new ComponentName(getServiceInfo().packageName, activityName)
         );
     }
 
diff --git a/core/java/com/android/internal/widget/MaxHeightFrameLayout.java b/core/java/com/android/internal/widget/MaxHeightFrameLayout.java
new file mode 100644
index 0000000..d65dddd
--- /dev/null
+++ b/core/java/com/android/internal/widget/MaxHeightFrameLayout.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Px;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+
+/**
+ * This custom subclass of FrameLayout enforces that its calculated height be no larger than the
+ * given maximum height (if any).
+ *
+ * @hide
+ */
+public class MaxHeightFrameLayout extends FrameLayout {
+
+    private int mMaxHeight = Integer.MAX_VALUE;
+
+    public MaxHeightFrameLayout(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public MaxHeightFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public MaxHeightFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public MaxHeightFrameLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.MaxHeightFrameLayout, defStyleAttr, defStyleRes);
+        saveAttributeDataForStyleable(context, R.styleable.MaxHeightFrameLayout,
+                attrs, a, defStyleAttr, defStyleRes);
+
+        setMaxHeight(a.getDimensionPixelSize(R.styleable.MaxHeightFrameLayout_maxHeight,
+                Integer.MAX_VALUE));
+    }
+
+    /**
+     * Gets the maximum height of this view, in pixels.
+     *
+     * @see #setMaxHeight(int)
+     *
+     * @attr ref android.R.styleable#MaxHeightFrameLayout_maxHeight
+     */
+    @Px
+    public int getMaxHeight() {
+        return mMaxHeight;
+    }
+
+    /**
+     * Sets the maximum height this view can have.
+     *
+     * @param maxHeight the maximum height, in pixels
+     *
+     * @see #getMaxHeight()
+     *
+     * @attr ref android.R.styleable#MaxHeightFrameLayout_maxHeight
+     */
+    public void setMaxHeight(@Px int maxHeight) {
+        mMaxHeight = maxHeight;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (MeasureSpec.getSize(heightMeasureSpec) > mMaxHeight) {
+            final int mode = MeasureSpec.getMode(heightMeasureSpec);
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, mode);
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+}
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 30c926c..7e2a5ac 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -17,6 +17,8 @@
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android/graphics/jni_runtime.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <jni_wrappers.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/jni_macros.h>
 #include <unicode/putil.h>
@@ -27,9 +29,6 @@
 #include <unordered_map>
 #include <vector>
 
-#include "android_view_InputDevice.h"
-#include "core_jni_helpers.h"
-#include "jni.h"
 #ifdef _WIN32
 #include <windows.h>
 #else
@@ -38,8 +37,6 @@
 #include <sys/stat.h>
 #endif
 
-#include <iostream>
-
 using namespace std;
 
 /*
@@ -49,12 +46,6 @@
  * (see AndroidRuntime.cpp).
  */
 
-static JavaVM* javaVM;
-static jclass bridge;
-static jclass layoutLog;
-static jmethodID getLogId;
-static jmethodID logMethodId;
-
 extern int register_android_os_Binder(JNIEnv* env);
 extern int register_libcore_util_NativeAllocationRegistry_Delegate(JNIEnv* env);
 
@@ -168,28 +159,9 @@
         }
     }
 
-    if (register_android_graphics_classes(env) < 0) {
-        return -1;
-    }
-
     return 0;
 }
 
-int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className,
-                                          const JNINativeMethod* gMethods, int numMethods) {
-    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
-}
-
-JNIEnv* AndroidRuntime::getJNIEnv() {
-    JNIEnv* env;
-    if (javaVM->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) return nullptr;
-    return env;
-}
-
-JavaVM* AndroidRuntime::getJavaVM() {
-    return javaVM;
-}
-
 static vector<string> parseCsv(const string& csvString) {
     vector<string> result;
     istringstream stream(csvString);
@@ -200,29 +172,6 @@
     return result;
 }
 
-void LayoutlibLogger(base::LogId, base::LogSeverity severity, const char* tag, const char* file,
-                     unsigned int line, const char* message) {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-    jint logPrio = severity;
-    jstring tagString = env->NewStringUTF(tag);
-    jstring messageString = env->NewStringUTF(message);
-
-    jobject bridgeLog = env->CallStaticObjectMethod(bridge, getLogId);
-
-    env->CallVoidMethod(bridgeLog, logMethodId, logPrio, tagString, messageString);
-
-    env->DeleteLocalRef(tagString);
-    env->DeleteLocalRef(messageString);
-    env->DeleteLocalRef(bridgeLog);
-}
-
-void LayoutlibAborter(const char* abort_message) {
-    // Layoutlib should not call abort() as it would terminate Studio.
-    // Throw an exception back to Java instead.
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-    jniThrowRuntimeException(env, "The Android framework has encountered a fatal error");
-}
-
 // This method has been copied/adapted from system/core/init/property_service.cpp
 // If the ro.product.cpu.abilist* properties have not been explicitly
 // set, derive them from ro.system.product.cpu.abilist* properties.
@@ -311,62 +260,49 @@
 #endif
 }
 
-static bool init_icu(const char* dataPath) {
-    void* addr = mmapFile(dataPath);
-    UErrorCode err = U_ZERO_ERROR;
-    udata_setCommonData(addr, &err);
-    if (err != U_ZERO_ERROR) {
-        return false;
-    }
-    return true;
-}
-
-// Creates an array of InputDevice from key character map files
-static void init_keyboard(JNIEnv* env, const vector<string>& keyboardPaths) {
-    jclass inputDevice = FindClassOrDie(env, "android/view/InputDevice");
-    jobjectArray inputDevicesArray =
-            env->NewObjectArray(keyboardPaths.size(), inputDevice, nullptr);
-    int keyboardId = 1;
-
-    for (const string& path : keyboardPaths) {
-        base::Result<std::shared_ptr<KeyCharacterMap>> charMap =
-                KeyCharacterMap::load(path, KeyCharacterMap::Format::BASE);
-
-        InputDeviceInfo info = InputDeviceInfo();
-        info.initialize(keyboardId, 0, 0, InputDeviceIdentifier(),
-                        "keyboard " + std::to_string(keyboardId), true, false,
-                        ui::LogicalDisplayId::DEFAULT);
-        info.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC);
-        info.setKeyCharacterMap(*charMap);
-
-        jobject inputDeviceObj = android_view_InputDevice_create(env, info);
-        if (inputDeviceObj) {
-            env->SetObjectArrayElement(inputDevicesArray, keyboardId - 1, inputDeviceObj);
-            env->DeleteLocalRef(inputDeviceObj);
+// Loads the ICU data file from the location specified in the system property ro.icu.data.path
+static void loadIcuData() {
+    string icuPath = base::GetProperty("ro.icu.data.path", "");
+    if (!icuPath.empty()) {
+        // Set the location of ICU data
+        void* addr = mmapFile(icuPath.c_str());
+        UErrorCode err = U_ZERO_ERROR;
+        udata_setCommonData(addr, &err);
+        if (err != U_ZERO_ERROR) {
+            ALOGE("Unable to load ICU data\n");
         }
-        keyboardId++;
     }
-
-    if (bridge == nullptr) {
-        bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge");
-        bridge = MakeGlobalRefOrDie(env, bridge);
-    }
-    jmethodID setInputManager = GetStaticMethodIDOrDie(env, bridge, "setInputManager",
-                                                       "([Landroid/view/InputDevice;)V");
-    env->CallStaticVoidMethod(bridge, setInputManager, inputDevicesArray);
-    env->DeleteLocalRef(inputDevicesArray);
 }
 
-} // namespace android
+static int register_android_core_classes(JNIEnv* env) {
+    jclass system = FindClassOrDie(env, "java/lang/System");
+    jmethodID getPropertyMethod =
+            GetStaticMethodIDOrDie(env, system, "getProperty",
+                                   "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
 
-using namespace android;
+    // Get the names of classes that need to register their native methods
+    auto nativesClassesJString =
+            (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
+                                                 env->NewStringUTF("core_native_classes"),
+                                                 env->NewStringUTF(""));
+    const char* nativesClassesArray = env->GetStringUTFChars(nativesClassesJString, nullptr);
+    string nativesClassesString(nativesClassesArray);
+    vector<string> classesToRegister = parseCsv(nativesClassesString);
+    env->ReleaseStringUTFChars(nativesClassesJString, nativesClassesArray);
+
+    if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) {
+        return JNI_ERR;
+    }
+
+    return 0;
+}
 
 // Called right before aborting by LOG_ALWAYS_FATAL. Print the pending exception.
 void abort_handler(const char* abort_message) {
     ALOGE("About to abort the process...");
 
-    JNIEnv* env = NULL;
-    if (javaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    if (env == nullptr) {
         ALOGE("vm->GetEnv() failed");
         return;
     }
@@ -377,107 +313,98 @@
     ALOGE("Aborting because: %s", abort_message);
 }
 
+// ------------------ Host implementation of AndroidRuntime ------------------
+
+/*static*/ JavaVM* AndroidRuntime::mJavaVM;
+
+/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className,
+                                                     const JNINativeMethod* gMethods,
+                                                     int numMethods) {
+    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
+}
+
+/*static*/ JNIEnv* AndroidRuntime::getJNIEnv() {
+    JNIEnv* env;
+    JavaVM* vm = AndroidRuntime::getJavaVM();
+    if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
+        return nullptr;
+    }
+    return env;
+}
+
+/*static*/ JavaVM* AndroidRuntime::getJavaVM() {
+    return mJavaVM;
+}
+
+/*static*/ int AndroidRuntime::startReg(JNIEnv* env) {
+    if (register_android_core_classes(env) < 0) {
+        return JNI_ERR;
+    }
+    if (register_android_graphics_classes(env) < 0) {
+        return JNI_ERR;
+    }
+    return 0;
+}
+
+void AndroidRuntime::onVmCreated(JNIEnv* env) {
+    env->GetJavaVM(&mJavaVM);
+}
+
+void AndroidRuntime::onStarted() {
+    property_initialize_ro_cpu_abilist();
+    loadIcuData();
+
+    // Use English locale for number format to ensure correct parsing of floats when using strtof
+    setlocale(LC_NUMERIC, "en_US.UTF-8");
+}
+
+void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    // Register native functions.
+    if (startReg(env) < 0) {
+        ALOGE("Unable to register all android native methods\n");
+    }
+    onStarted();
+}
+
+AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength)
+      : mExitWithoutCleanup(false), mArgBlockStart(argBlockStart), mArgBlockLength(argBlockLength) {
+    init_android_graphics();
+}
+
+AndroidRuntime::~AndroidRuntime() {}
+
+// Version of AndroidRuntime to run on host
+class HostRuntime : public AndroidRuntime {
+public:
+    HostRuntime() : AndroidRuntime(nullptr, 0) {}
+
+    void onVmCreated(JNIEnv* env) override {
+        AndroidRuntime::onVmCreated(env);
+        // initialize logging, so ANDROD_LOG_TAGS env variable is respected
+        android::base::InitLogging(nullptr, android::base::StderrLogger, abort_handler);
+    }
+
+    void onStarted() override {
+        AndroidRuntime::onStarted();
+    }
+};
+
+} // namespace android
+
+using namespace android;
+
 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
-    javaVM = vm;
     JNIEnv* env = nullptr;
     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
         return JNI_ERR;
     }
 
-    __android_log_set_aborter(abort_handler);
+    Vector<String8> args;
+    HostRuntime runtime;
 
-    init_android_graphics();
-
-    // Configuration is stored as java System properties.
-    // Get a reference to System.getProperty
-    jclass system = FindClassOrDie(env, "java/lang/System");
-    jmethodID getPropertyMethod =
-            GetStaticMethodIDOrDie(env, system, "getProperty",
-                                   "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
-
-    // Java system properties that contain LayoutLib config. The initial values in the map
-    // are the default values if the property is not specified.
-    std::unordered_map<std::string, std::string> systemProperties =
-            {{"core_native_classes", ""},
-             {"register_properties_during_load", ""},
-             {"icu.data.path", ""},
-             {"use_bridge_for_logging", ""},
-             {"keyboard_paths", ""}};
-
-    for (auto& [name, defaultValue] : systemProperties) {
-        jstring propertyString =
-                (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
-                                                     env->NewStringUTF(name.c_str()),
-                                                     env->NewStringUTF(defaultValue.c_str()));
-        const char* propertyChars = env->GetStringUTFChars(propertyString, 0);
-        systemProperties[name] = string(propertyChars);
-        env->ReleaseStringUTFChars(propertyString, propertyChars);
-    }
-    // Get the names of classes that need to register their native methods
-    vector<string> classesToRegister = parseCsv(systemProperties["core_native_classes"]);
-
-    if (systemProperties["register_properties_during_load"] == "true") {
-        // Set the system properties first as they could be used in the static initialization of
-        // other classes
-        if (register_android_os_SystemProperties(env) < 0) {
-            return JNI_ERR;
-        }
-        classesToRegister.erase(find(classesToRegister.begin(), classesToRegister.end(),
-                                     "android.os.SystemProperties"));
-        bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge");
-        bridge = MakeGlobalRefOrDie(env, bridge);
-        jmethodID setSystemPropertiesMethod =
-                GetStaticMethodIDOrDie(env, bridge, "setSystemProperties", "()V");
-        env->CallStaticVoidMethod(bridge, setSystemPropertiesMethod);
-        property_initialize_ro_cpu_abilist();
-    }
-
-    if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) {
-        return JNI_ERR;
-    }
-
-    if (!systemProperties["icu.data.path"].empty()) {
-        // Set the location of ICU data
-        bool icuInitialized = init_icu(systemProperties["icu.data.path"].c_str());
-        if (!icuInitialized) {
-            return JNI_ERR;
-        }
-    }
-
-    if (systemProperties["use_bridge_for_logging"] == "true") {
-        layoutLog = FindClassOrDie(env, "com/android/ide/common/rendering/api/ILayoutLog");
-        layoutLog = MakeGlobalRefOrDie(env, layoutLog);
-        logMethodId = GetMethodIDOrDie(env, layoutLog, "logAndroidFramework",
-                                       "(ILjava/lang/String;Ljava/lang/String;)V");
-        if (bridge == nullptr) {
-            bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge");
-            bridge = MakeGlobalRefOrDie(env, bridge);
-        }
-        getLogId = GetStaticMethodIDOrDie(env, bridge, "getLog",
-                                          "()Lcom/android/ide/common/rendering/api/ILayoutLog;");
-        android::base::SetLogger(LayoutlibLogger);
-        android::base::SetAborter(LayoutlibAborter);
-    } else {
-        // initialize logging, so ANDROD_LOG_TAGS env variable is respected
-        android::base::InitLogging(nullptr, android::base::StderrLogger);
-    }
-
-    // Use English locale for number format to ensure correct parsing of floats when using strtof
-    setlocale(LC_NUMERIC, "en_US.UTF-8");
-
-    if (!systemProperties["keyboard_paths"].empty()) {
-        vector<string> keyboardPaths = parseCsv(systemProperties["keyboard_paths"]);
-        init_keyboard(env, keyboardPaths);
-    } else {
-        fprintf(stderr, "Skip initializing keyboard\n");
-    }
+    runtime.onVmCreated(env);
+    runtime.start("HostRuntime", args, false);
 
     return JNI_VERSION_1_6;
 }
-
-JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) {
-    JNIEnv* env = nullptr;
-    vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
-    env->DeleteGlobalRef(bridge);
-    env->DeleteGlobalRef(layoutLog);
-}
diff --git a/core/res/res/drawable/ic_ime_switcher_new.xml b/core/res/res/drawable/ic_ime_switcher_new.xml
new file mode 100644
index 0000000..04f4a25
--- /dev/null
+++ b/core/res/res/drawable/ic_ime_switcher_new.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="20dp"
+        android:height="20dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM18.92,8h-2.95c-0.32,-1.25 -0.78,-2.45 -1.38,-3.56 1.84,0.63 3.37,1.91 4.33,3.56zM12,4.04c0.83,1.2 1.48,2.53 1.91,3.96h-3.82c0.43,-1.43 1.08,-2.76 1.91,-3.96zM4.26,14C4.1,13.36 4,12.69 4,12s0.1,-1.36 0.26,-2h3.38c-0.08,0.66 -0.14,1.32 -0.14,2 0,0.68 0.06,1.34 0.14,2L4.26,14zM5.08,16h2.95c0.32,1.25 0.78,2.45 1.38,3.56 -1.84,-0.63 -3.37,-1.9 -4.33,-3.56zM8.03,8L5.08,8c0.96,-1.66 2.49,-2.93 4.33,-3.56C8.81,5.55 8.35,6.75 8.03,8zM12,19.96c-0.83,-1.2 -1.48,-2.53 -1.91,-3.96h3.82c-0.43,1.43 -1.08,2.76 -1.91,3.96zM14.34,14L9.66,14c-0.09,-0.66 -0.16,-1.32 -0.16,-2 0,-0.68 0.07,-1.35 0.16,-2h4.68c0.09,0.65 0.16,1.32 0.16,2 0,0.68 -0.07,1.34 -0.16,2zM14.59,19.56c0.6,-1.11 1.06,-2.31 1.38,-3.56h2.95c-0.96,1.65 -2.49,2.93 -4.33,3.56zM16.36,14c0.08,-0.66 0.14,-1.32 0.14,-2 0,-0.68 -0.06,-1.34 -0.14,-2h3.38c0.16,0.64 0.26,1.31 0.26,2s-0.1,1.36 -0.26,2h-3.38z"
+        android:fillColor="#FFFFFFFF"/>
+</vector>
diff --git a/core/res/res/drawable/input_method_switch_button.xml b/core/res/res/drawable/input_method_switch_button.xml
new file mode 100644
index 0000000..396d81e
--- /dev/null
+++ b/core/res/res/drawable/input_method_switch_button.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    android:insetTop="6dp"
+    android:insetBottom="6dp">
+    <ripple android:color="?android:attr/colorControlHighlight">
+        <item android:id="@android:id/mask">
+            <shape android:shape="rectangle">
+                <corners android:radius="28dp"/>
+                <solid android:color="@color/white"/>
+            </shape>
+        </item>
+
+        <item>
+            <shape android:shape="rectangle">
+                <corners android:radius="28dp"/>
+                <solid android:color="@color/transparent"/>
+                <stroke android:color="?attr/materialColorPrimary"
+                    android:width="1dp"/>
+                <padding android:left="16dp"
+                    android:top="8dp"
+                    android:right="16dp"
+                    android:bottom="8dp"/>
+            </shape>
+        </item>
+    </ripple>
+</inset>
diff --git a/core/res/res/drawable/input_method_switch_item_background.xml b/core/res/res/drawable/input_method_switch_item_background.xml
new file mode 100644
index 0000000..eb7a246
--- /dev/null
+++ b/core/res/res/drawable/input_method_switch_item_background.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/list_highlight_material">
+    <item android:id="@id/mask">
+        <shape android:shape="rectangle">
+            <corners android:radius="28dp"/>
+            <solid android:color="@color/white"/>
+        </shape>
+    </item>
+
+    <item>
+        <selector>
+            <item android:state_activated="true">
+                <shape android:shape="rectangle">
+                    <corners android:radius="28dp"/>
+                    <solid android:color="?attr/materialColorSecondaryContainer"/>
+                </shape>
+            </item>
+        </selector>
+    </item>
+</ripple>
diff --git a/core/res/res/layout/input_method_switch_dialog_new.xml b/core/res/res/layout/input_method_switch_dialog_new.xml
new file mode 100644
index 0000000..5a4d6b1
--- /dev/null
+++ b/core/res/res/layout/input_method_switch_dialog_new.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <com.android.internal.widget.MaxHeightFrameLayout
+        android:layout_width="320dp"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:maxHeight="373dp">
+
+        <com.android.internal.widget.RecyclerView
+            android:id="@+id/list"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingVertical="8dp"
+            android:clipToPadding="false"
+            android:layoutManager="com.android.internal.widget.LinearLayoutManager"/>
+
+    </com.android.internal.widget.MaxHeightFrameLayout>
+
+    <LinearLayout
+        style="?android:attr/buttonBarStyle"
+        android:id="@+id/button_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:paddingHorizontal="16dp"
+        android:paddingTop="8dp"
+        android:paddingBottom="16dp"
+        android:visibility="gone">
+
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"/>
+
+        <Button
+            style="?attr/buttonBarButtonStyle"
+            android:id="@+id/button1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@drawable/input_method_switch_button"
+            android:layout_gravity="end"
+            android:text="@string/input_method_language_settings"
+            android:fontFamily="google-sans-text"
+            android:textAppearance="?attr/textAppearance"
+            android:visibility="gone"/>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/core/res/res/layout/input_method_switch_item_new.xml b/core/res/res/layout/input_method_switch_item_new.xml
new file mode 100644
index 0000000..16a97c4
--- /dev/null
+++ b/core/res/res/layout/input_method_switch_item_new.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingHorizontal="16dp"
+    android:paddingBottom="8dp">
+
+    <View
+        android:id="@+id/divider"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="?attr/materialColorSurfaceVariant"
+        android:layout_marginStart="20dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="24dp"
+        android:layout_marginBottom="12dp"
+        android:visibility="gone"/>
+
+    <TextView
+        android:id="@+id/header_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:ellipsize="end"
+        android:singleLine="true"
+        android:fontFamily="google-sans-text"
+        android:textAppearance="?attr/textAppearance"
+        android:textColor="?attr/materialColorPrimary"
+        android:visibility="gone"/>
+
+    <LinearLayout
+        android:id="@+id/list_item"
+        android:layout_width="match_parent"
+        android:layout_height="72dp"
+        android:background="@drawable/input_method_switch_item_background"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+        android:paddingStart="20dp"
+        android:paddingEnd="24dp">
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="start|center_vertical"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ellipsize="marquee"
+                android:singleLine="true"
+                android:fontFamily="google-sans-text"
+                android:textAppearance="?attr/textAppearanceListItem"/>
+
+        </LinearLayout>
+
+        <ImageView
+            android:id="@+id/image"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:gravity="center_vertical"
+            android:layout_marginStart="12dp"
+            android:src="@drawable/ic_check_24dp"
+            android:tint="?attr/materialColorOnSurface"
+            android:visibility="gone"/>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 0975eda..7cc9e13 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5243,6 +5243,11 @@
              the VISIBLE or INVISIBLE state when measuring. Defaults to false. -->
         <attr name="measureAllChildren" format="boolean" />
     </declare-styleable>
+    <!-- @hide -->
+    <declare-styleable name="MaxHeightFrameLayout">
+        <!-- An optional argument to supply a maximum height for this view. -->
+        <attr name="maxHeight" format="dimension" />
+    </declare-styleable>
     <declare-styleable name="ExpandableListView">
         <!-- Indicator shown beside the group View. This can be a stateful Drawable. -->
         <attr name="groupIndicator" format="reference" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 46b15416..ec865f6 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3880,6 +3880,8 @@
 
     <!-- Title of the pop-up dialog in which the user switches keyboard, also known as input method. -->
     <string name="select_input_method">Choose input method</string>
+    <!-- Button to access the language settings of the current input method. [CHAR LIMIT=50]-->
+    <string name="input_method_language_settings">Language Settings</string>
     <!-- Summary text of a toggle switch to enable/disable use of the IME while a physical
          keyboard is connected -->
     <string name="show_ime">Keep it on screen while physical keyboard is active</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c50b961..fcafdae 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1577,6 +1577,8 @@
   <java-symbol type="layout" name="input_method" />
   <java-symbol type="layout" name="input_method_extract_view" />
   <java-symbol type="layout" name="input_method_switch_item" />
+  <java-symbol type="layout" name="input_method_switch_item_new" />
+  <java-symbol type="layout" name="input_method_switch_dialog_new" />
   <java-symbol type="layout" name="input_method_switch_dialog_title" />
   <java-symbol type="layout" name="js_prompt" />
   <java-symbol type="layout" name="list_content_simple" />
@@ -2552,6 +2554,7 @@
   <java-symbol type="dimen" name="input_method_nav_key_button_ripple_max_width" />
   <java-symbol type="drawable" name="ic_ime_nav_back" />
   <java-symbol type="drawable" name="ic_ime_switcher" />
+  <java-symbol type="drawable" name="ic_ime_switcher_new" />
   <java-symbol type="id" name="input_method_nav_back" />
   <java-symbol type="id" name="input_method_nav_buttons" />
   <java-symbol type="id" name="input_method_nav_center_group" />
@@ -5400,6 +5403,7 @@
   <java-symbol type="style" name="Theme.DeviceDefault.DialogWhenLarge" />
   <java-symbol type="style" name="Theme.DeviceDefault.DocumentsUI" />
   <java-symbol type="style" name="Theme.DeviceDefault.InputMethod" />
+  <java-symbol type="style" name="Theme.DeviceDefault.InputMethodSwitcherDialog" />
   <java-symbol type="style" name="Theme.DeviceDefault.Light.DarkActionBar" />
   <java-symbol type="style" name="Theme.DeviceDefault.Light.Dialog.FixedSize" />
   <java-symbol type="style" name="Theme.DeviceDefault.Light.Dialog.MinWidth" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 382ff04..f5c6738 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -6179,4 +6179,10 @@
         <item name="colorListDivider">@color/list_divider_opacity_device_default_light</item>
         <item name="opacityListDivider">@color/list_divider_opacity_device_default_light</item>
     </style>
+
+    <!--  Device default theme for the Input Method Switcher dialog.  -->
+    <style name="Theme.DeviceDefault.InputMethodSwitcherDialog" parent="Theme.DeviceDefault.Dialog.Alert.DayNight">
+        <item name="windowMinWidthMajor">@null</item>
+        <item name="windowMinWidthMinor">@null</item>
+    </style>
 </resources>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
index 235b9bf..fc3dc14 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
@@ -168,6 +168,16 @@
         }
     }
 
+    /** Whether any animation is currently running. */
+    @JvmStatic
+    fun isAnyAnimationRunning(): Boolean {
+        for (target in allAnimatedObjects) {
+            val animator = PhysicsAnimator.getInstance(target)
+            if (animator.isRunning()) return true
+        }
+        return false
+    }
+
     /**
      * Blocks the calling thread until the first animation frame in which predicate returns true. If
      * the given object isn't animating, returns without blocking.
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 8e2f7c1..97a45fb 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1196,4 +1196,14 @@
    metadata {
         purpose: PURPOSE_BUGFIX
    }
+}
+
+flag {
+   name: "sim_pin_talkback_fix_for_double_submit"
+   namespace: "systemui"
+   description: "The SIM PIN entry screens show the wrong message due"
+   bug: "346932439"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 20b1303..78ba7de 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -64,6 +64,7 @@
     internal val orientation: Orientation,
     internal val coroutineScope: CoroutineScope,
 ) : DraggableHandler {
+    internal val nestedScrollKey = Any()
     /** The [DraggableHandler] can only have one active [DragController] at a time. */
     private var dragController: DragControllerImpl? = null
 
@@ -912,9 +913,9 @@
 internal class NestedScrollHandlerImpl(
     private val layoutImpl: SceneTransitionLayoutImpl,
     private val orientation: Orientation,
-    private val topOrLeftBehavior: NestedScrollBehavior,
-    private val bottomOrRightBehavior: NestedScrollBehavior,
-    private val isExternalOverscrollGesture: () -> Boolean,
+    internal var topOrLeftBehavior: NestedScrollBehavior,
+    internal var bottomOrRightBehavior: NestedScrollBehavior,
+    internal var isExternalOverscrollGesture: () -> Boolean,
     private val pointersInfoOwner: PointersInfoOwner,
 ) {
     private val layoutState = layoutImpl.state
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 615d393..2b78b5a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -41,7 +41,6 @@
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.ObserverModifierNode
 import androidx.compose.ui.node.PointerInputModifierNode
-import androidx.compose.ui.node.TraversableNode
 import androidx.compose.ui.node.currentValueOf
 import androidx.compose.ui.node.findNearestAncestor
 import androidx.compose.ui.node.observeReads
@@ -139,16 +138,12 @@
     DelegatingNode(),
     PointerInputModifierNode,
     CompositionLocalConsumerModifierNode,
-    TraversableNode,
-    PointersInfoOwner,
     ObserverModifierNode {
     private val pointerInputHandler: suspend PointerInputScope.() -> Unit = { pointerInput() }
     private val delegate = delegate(SuspendingPointerInputModifierNode(pointerInputHandler))
     private val velocityTracker = VelocityTracker()
     private var previousEnabled: Boolean = false
 
-    override val traverseKey: Any = TRAVERSE_KEY
-
     var enabled: () -> Boolean = enabled
         set(value) {
             // Reset the pointer input whenever enabled changed.
@@ -208,7 +203,7 @@
     private var startedPosition: Offset? = null
     private var pointersDown: Int = 0
 
-    override fun pointersInfo(): PointersInfo {
+    internal fun pointersInfo(): PointersInfo {
         return PointersInfo(
             startedPosition = startedPosition,
             // Note: We could have 0 pointers during fling or for other reasons.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index ddff2f7..945043d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -18,12 +18,13 @@
 
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode
-import androidx.compose.ui.node.DelegatableNode
 import androidx.compose.ui.node.DelegatingNode
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.InspectorInfo
-import com.android.compose.nestedscroll.PriorityNestedScrollConnection
 
 /**
  * Defines the behavior of the [SceneTransitionLayout] when a scrollable component is scrolled.
@@ -67,7 +68,11 @@
      * In addition, during scene transitions, scroll events are consumed by the
      * [SceneTransitionLayout] instead of the scrollable component.
      */
-    EdgeAlways(canStartOnPostFling = true),
+    EdgeAlways(canStartOnPostFling = true);
+
+    companion object {
+        val Default = EdgeNoPreview
+    }
 }
 
 internal fun Modifier.nestedScrollToScene(
@@ -122,37 +127,60 @@
 }
 
 private class NestedScrollToSceneNode(
-    layoutImpl: SceneTransitionLayoutImpl,
-    orientation: Orientation,
-    topOrLeftBehavior: NestedScrollBehavior,
-    bottomOrRightBehavior: NestedScrollBehavior,
-    isExternalOverscrollGesture: () -> Boolean,
+    private var layoutImpl: SceneTransitionLayoutImpl,
+    private var orientation: Orientation,
+    private var topOrLeftBehavior: NestedScrollBehavior,
+    private var bottomOrRightBehavior: NestedScrollBehavior,
+    private var isExternalOverscrollGesture: () -> Boolean,
 ) : DelegatingNode() {
-    lateinit var pointersInfoOwner: PointersInfoOwner
-    private var priorityNestedScrollConnection: PriorityNestedScrollConnection =
-        scenePriorityNestedScrollConnection(
-            layoutImpl = layoutImpl,
-            orientation = orientation,
-            topOrLeftBehavior = topOrLeftBehavior,
-            bottomOrRightBehavior = bottomOrRightBehavior,
-            isExternalOverscrollGesture = isExternalOverscrollGesture,
-            pointersInfoOwner = { pointersInfoOwner.pointersInfo() }
-        )
+    private var scrollBehaviorOwner: ScrollBehaviorOwner? = null
 
-    private var nestedScrollNode: DelegatableNode =
-        nestedScrollModifierNode(
-            connection = priorityNestedScrollConnection,
-            dispatcher = null,
-        )
+    private fun requireScrollBehaviorOwner(): ScrollBehaviorOwner {
+        var behaviorOwner = scrollBehaviorOwner
+        if (behaviorOwner == null) {
+            behaviorOwner = requireScrollBehaviorOwner(layoutImpl.draggableHandler(orientation))
+            scrollBehaviorOwner = behaviorOwner
+        }
+        return behaviorOwner
+    }
 
-    override fun onAttach() {
-        pointersInfoOwner = requireAncestorPointersInfoOwner()
-        delegate(nestedScrollNode)
+    private val updateScrollBehaviorsConnection =
+        object : NestedScrollConnection {
+            /**
+             * When using [NestedScrollConnection.onPostScroll], we can specify the desired behavior
+             * before our parent components. This gives them the option to override our behavior if
+             * they choose.
+             *
+             * The behavior can be communicated at every scroll gesture to ensure that the hierarchy
+             * is respected, even if one of our descendant nodes changes behavior after we set it.
+             */
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource,
+            ): Offset {
+                // If we have some remaining scroll, that scroll can be used to initiate a
+                // transition between scenes. We can assume that the behavior is only needed if
+                // there is some remaining amount.
+                if (available != Offset.Zero) {
+                    requireScrollBehaviorOwner()
+                        .updateScrollBehaviors(
+                            topOrLeftBehavior = topOrLeftBehavior,
+                            bottomOrRightBehavior = bottomOrRightBehavior,
+                            isExternalOverscrollGesture = isExternalOverscrollGesture,
+                        )
+                }
+
+                return Offset.Zero
+            }
+        }
+
+    init {
+        delegate(nestedScrollModifierNode(updateScrollBehaviorsConnection, dispatcher = null))
     }
 
     override fun onDetach() {
-        // Make sure we reset the scroll connection when this modifier is removed from composition
-        priorityNestedScrollConnection.reset()
+        scrollBehaviorOwner = null
     }
 
     fun update(
@@ -162,43 +190,10 @@
         bottomOrRightBehavior: NestedScrollBehavior,
         isExternalOverscrollGesture: () -> Boolean,
     ) {
-        // Clean up the old nested scroll connection
-        priorityNestedScrollConnection.reset()
-        undelegate(nestedScrollNode)
-
-        // Create a new nested scroll connection
-        priorityNestedScrollConnection =
-            scenePriorityNestedScrollConnection(
-                layoutImpl = layoutImpl,
-                orientation = orientation,
-                topOrLeftBehavior = topOrLeftBehavior,
-                bottomOrRightBehavior = bottomOrRightBehavior,
-                isExternalOverscrollGesture = isExternalOverscrollGesture,
-                pointersInfoOwner = pointersInfoOwner,
-            )
-        nestedScrollNode =
-            nestedScrollModifierNode(
-                connection = priorityNestedScrollConnection,
-                dispatcher = null,
-            )
-        delegate(nestedScrollNode)
+        this.layoutImpl = layoutImpl
+        this.orientation = orientation
+        this.topOrLeftBehavior = topOrLeftBehavior
+        this.bottomOrRightBehavior = bottomOrRightBehavior
+        this.isExternalOverscrollGesture = isExternalOverscrollGesture
     }
 }
-
-private fun scenePriorityNestedScrollConnection(
-    layoutImpl: SceneTransitionLayoutImpl,
-    orientation: Orientation,
-    topOrLeftBehavior: NestedScrollBehavior,
-    bottomOrRightBehavior: NestedScrollBehavior,
-    isExternalOverscrollGesture: () -> Boolean,
-    pointersInfoOwner: PointersInfoOwner,
-) =
-    NestedScrollHandlerImpl(
-            layoutImpl = layoutImpl,
-            orientation = orientation,
-            topOrLeftBehavior = topOrLeftBehavior,
-            bottomOrRightBehavior = bottomOrRightBehavior,
-            isExternalOverscrollGesture = isExternalOverscrollGesture,
-            pointersInfoOwner = pointersInfoOwner,
-        )
-        .connection
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 0c467b1..82275a9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -207,8 +207,8 @@
      * @param rightBehavior when we should perform the overscroll animation at the right.
      */
     fun Modifier.horizontalNestedScrollToScene(
-        leftBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
-        rightBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
+        leftBehavior: NestedScrollBehavior = NestedScrollBehavior.Default,
+        rightBehavior: NestedScrollBehavior = NestedScrollBehavior.Default,
         isExternalOverscrollGesture: () -> Boolean = { false },
     ): Modifier
 
@@ -220,8 +220,8 @@
      * @param bottomBehavior when we should perform the overscroll animation at the bottom.
      */
     fun Modifier.verticalNestedScrollToScene(
-        topBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
-        bottomBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
+        topBehavior: NestedScrollBehavior = NestedScrollBehavior.Default,
+        bottomBehavior: NestedScrollBehavior = NestedScrollBehavior.Default,
         isExternalOverscrollGesture: () -> Boolean = { false },
     ): Modifier
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index aeb6262..b8010f2 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -20,11 +20,15 @@
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode
 import androidx.compose.ui.input.pointer.PointerEvent
 import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.node.DelegatableNode
 import androidx.compose.ui.node.DelegatingNode
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.PointerInputModifierNode
+import androidx.compose.ui.node.TraversableNode
+import androidx.compose.ui.node.findNearestAncestor
 import androidx.compose.ui.unit.IntSize
 
 /**
@@ -53,7 +57,7 @@
     draggableHandler: DraggableHandlerImpl,
     swipeDetector: SwipeDetector,
 ) : DelegatingNode(), PointerInputModifierNode {
-    private val delegate =
+    private val multiPointerDraggableNode =
         delegate(
             MultiPointerDraggableNode(
                 orientation = draggableHandler.orientation,
@@ -74,21 +78,41 @@
                 // Make sure to update the delegate orientation. Note that this will automatically
                 // reset the underlying pointer input handler, so previous gestures will be
                 // cancelled.
-                delegate.orientation = value.orientation
+                multiPointerDraggableNode.orientation = value.orientation
             }
         }
 
+    private val nestedScrollHandlerImpl =
+        NestedScrollHandlerImpl(
+            layoutImpl = draggableHandler.layoutImpl,
+            orientation = draggableHandler.orientation,
+            topOrLeftBehavior = NestedScrollBehavior.Default,
+            bottomOrRightBehavior = NestedScrollBehavior.Default,
+            isExternalOverscrollGesture = { false },
+            pointersInfoOwner = { multiPointerDraggableNode.pointersInfo() },
+        )
+
+    init {
+        delegate(nestedScrollModifierNode(nestedScrollHandlerImpl.connection, dispatcher = null))
+        delegate(ScrollBehaviorOwnerNode(draggableHandler.nestedScrollKey, nestedScrollHandlerImpl))
+    }
+
+    override fun onDetach() {
+        // Make sure we reset the scroll connection when this modifier is removed from composition
+        nestedScrollHandlerImpl.connection.reset()
+    }
+
     override fun onPointerEvent(
         pointerEvent: PointerEvent,
         pass: PointerEventPass,
         bounds: IntSize,
-    ) = delegate.onPointerEvent(pointerEvent, pass, bounds)
+    ) = multiPointerDraggableNode.onPointerEvent(pointerEvent, pass, bounds)
 
-    override fun onCancelPointerInput() = delegate.onCancelPointerInput()
+    override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput()
 
     private fun enabled(): Boolean {
         return draggableHandler.isDrivingTransition ||
-            currentScene().shouldEnableSwipes(delegate.orientation)
+            currentScene().shouldEnableSwipes(multiPointerDraggableNode.orientation)
     }
 
     private fun currentScene(): Scene {
@@ -118,3 +142,43 @@
         return currentScene().shouldEnableSwipes(oppositeOrientation)
     }
 }
+
+/** Find the [ScrollBehaviorOwner] for the current orientation. */
+internal fun DelegatableNode.requireScrollBehaviorOwner(
+    draggableHandler: DraggableHandlerImpl
+): ScrollBehaviorOwner {
+    val ancestorNode =
+        checkNotNull(findNearestAncestor(draggableHandler.nestedScrollKey)) {
+            "This should never happen! Couldn't find a ScrollBehaviorOwner. " +
+                "Are we inside an SceneTransitionLayout?"
+        }
+    return ancestorNode as ScrollBehaviorOwner
+}
+
+internal fun interface ScrollBehaviorOwner {
+    fun updateScrollBehaviors(
+        topOrLeftBehavior: NestedScrollBehavior,
+        bottomOrRightBehavior: NestedScrollBehavior,
+        isExternalOverscrollGesture: () -> Boolean,
+    )
+}
+
+/**
+ * We need a node that receives the desired behavior.
+ *
+ * TODO(b/353234530) move this logic into [SwipeToSceneNode]
+ */
+private class ScrollBehaviorOwnerNode(
+    override val traverseKey: Any,
+    val nestedScrollHandlerImpl: NestedScrollHandlerImpl
+) : Modifier.Node(), TraversableNode, ScrollBehaviorOwner {
+    override fun updateScrollBehaviors(
+        topOrLeftBehavior: NestedScrollBehavior,
+        bottomOrRightBehavior: NestedScrollBehavior,
+        isExternalOverscrollGesture: () -> Boolean
+    ) {
+        nestedScrollHandlerImpl.topOrLeftBehavior = topOrLeftBehavior
+        nestedScrollHandlerImpl.bottomOrRightBehavior = bottomOrRightBehavior
+        nestedScrollHandlerImpl.isExternalOverscrollGesture = isExternalOverscrollGesture
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 7988e0e..c91151e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -797,8 +797,6 @@
                 scene(SceneB, userActions = mapOf(Swipe.Up to SceneA)) {
                     Box(
                         Modifier
-                            // Unconsumed scroll gesture will be intercepted by STL
-                            .verticalNestedScrollToScene()
                             // A scrollable that does not consume the scroll gesture
                             .scrollable(
                                 rememberScrollableState(consumeScrollDelta = { 0f }),
@@ -875,8 +873,6 @@
                 ) {
                     Box(
                         Modifier
-                            // Unconsumed scroll gesture will be intercepted by STL
-                            .verticalNestedScrollToScene()
                             // A scrollable that does not consume the scroll gesture
                             .scrollable(
                                 rememberScrollableState(consumeScrollDelta = { 0f }),
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
new file mode 100644
index 0000000..311a580
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.foundation.gestures.Orientation.Vertical
+import androidx.compose.foundation.gestures.rememberScrollableState
+import androidx.compose.foundation.gestures.scrollable
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.subjects.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class NestedScrollToSceneTest {
+    @get:Rule val rule = createComposeRule()
+
+    private var touchSlop = 0f
+    private val layoutWidth: Dp = 200.dp
+    private val layoutHeight = 400.dp
+
+    private fun setup2ScenesAndScrollTouchSlop(
+        modifierSceneA: @Composable SceneScope.() -> Modifier = { Modifier },
+    ): MutableSceneTransitionLayoutState {
+        val state =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(SceneA, transitions = EmptyTestTransitions)
+            }
+
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            SceneTransitionLayout(
+                state = state,
+                modifier = Modifier.size(layoutWidth, layoutHeight)
+            ) {
+                scene(SceneA, userActions = mapOf(Swipe.Up to SceneB)) {
+                    Spacer(modifierSceneA().fillMaxSize())
+                }
+                scene(SceneB, userActions = mapOf(Swipe.Down to SceneA)) {
+                    Spacer(Modifier.fillMaxSize())
+                }
+            }
+        }
+
+        pointerDownAndScrollTouchSlop()
+
+        assertThat(state.transitionState).isIdle()
+
+        return state
+    }
+
+    private fun pointerDownAndScrollTouchSlop() {
+        rule.onRoot().performTouchInput {
+            val middleTop = Offset((layoutWidth / 2).toPx(), 0f)
+            down(middleTop)
+            // Scroll touchSlop
+            moveBy(Offset(0f, touchSlop), delayMillis = 1_000)
+        }
+    }
+
+    private fun scrollDown(percent: Float = 1f) {
+        rule.onRoot().performTouchInput {
+            moveBy(Offset(0f, layoutHeight.toPx() * percent), delayMillis = 1_000)
+        }
+    }
+
+    private fun scrollUp(percent: Float = 1f) = scrollDown(-percent)
+
+    private fun pointerUp() {
+        rule.onRoot().performTouchInput { up() }
+    }
+
+    @Test
+    fun scrollableElementsInSTL_shouldHavePriority() {
+        val state = setup2ScenesAndScrollTouchSlop {
+            Modifier
+                // A scrollable that consumes the scroll gesture
+                .scrollable(rememberScrollableState { it }, Vertical)
+        }
+
+        scrollUp(percent = 0.5f)
+
+        // Consumed by the scrollable element
+        assertThat(state.transitionState).isIdle()
+    }
+
+    @Test
+    fun unconsumedScrollEvents_canBeConsumedBySTLByDefault() {
+        val state = setup2ScenesAndScrollTouchSlop {
+            Modifier
+                // A scrollable that does not consume the scroll gesture
+                .scrollable(rememberScrollableState { 0f }, Vertical)
+        }
+
+        scrollUp(percent = 0.5f)
+        // STL will start a transition with the remaining scroll
+        val transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasProgress(0.5f)
+
+        scrollUp(percent = 1f)
+        assertThat(transition).hasProgress(1.5f)
+    }
+
+    @Test
+    fun customizeStlNestedScrollBehavior_DuringTransitionBetweenScenes() {
+        var canScroll = true
+        val state = setup2ScenesAndScrollTouchSlop {
+            Modifier.verticalNestedScrollToScene(
+                    bottomBehavior = NestedScrollBehavior.DuringTransitionBetweenScenes
+                )
+                .scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical)
+        }
+
+        scrollUp(percent = 0.5f)
+        assertThat(state.transitionState).isIdle()
+
+        // Reach the end of the scrollable element
+        canScroll = false
+        scrollUp(percent = 0.5f)
+        assertThat(state.transitionState).isIdle()
+
+        pointerUp()
+        assertThat(state.transitionState).isIdle()
+
+        // Start a new gesture
+        pointerDownAndScrollTouchSlop()
+        scrollUp(percent = 0.5f)
+        assertThat(state.transitionState).isIdle()
+    }
+
+    @Test
+    fun customizeStlNestedScrollBehavior_EdgeNoPreview() {
+        var canScroll = true
+        val state = setup2ScenesAndScrollTouchSlop {
+            Modifier.verticalNestedScrollToScene(
+                    bottomBehavior = NestedScrollBehavior.EdgeNoPreview
+                )
+                .scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical)
+        }
+
+        scrollUp(percent = 0.5f)
+        assertThat(state.transitionState).isIdle()
+
+        // Reach the end of the scrollable element
+        canScroll = false
+        scrollUp(percent = 0.5f)
+        assertThat(state.transitionState).isIdle()
+
+        pointerUp()
+        assertThat(state.transitionState).isIdle()
+
+        // Start a new gesture
+        pointerDownAndScrollTouchSlop()
+        scrollUp(percent = 0.5f)
+        val transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasProgress(0.5f)
+
+        pointerUp()
+        rule.waitForIdle()
+        assertThat(state.transitionState).isIdle()
+        assertThat(state.transitionState).hasCurrentScene(SceneB)
+    }
+
+    @Test
+    fun customizeStlNestedScrollBehavior_EdgeWithPreview() {
+        var canScroll = true
+        val state = setup2ScenesAndScrollTouchSlop {
+            Modifier.verticalNestedScrollToScene(
+                    bottomBehavior = NestedScrollBehavior.EdgeWithPreview
+                )
+                .scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical)
+        }
+
+        scrollUp(percent = 0.5f)
+        assertThat(state.transitionState).isIdle()
+
+        // Reach the end of the scrollable element
+        canScroll = false
+        scrollUp(percent = 0.5f)
+        val transition1 = assertThat(state.transitionState).isTransition()
+        assertThat(transition1).hasProgress(0.5f)
+
+        pointerUp()
+        rule.waitForIdle()
+        assertThat(state.transitionState).isIdle()
+        assertThat(state.transitionState).hasCurrentScene(SceneA)
+
+        // Start a new gesture
+        pointerDownAndScrollTouchSlop()
+        scrollUp(percent = 0.5f)
+        val transition2 = assertThat(state.transitionState).isTransition()
+        assertThat(transition2).hasProgress(0.5f)
+
+        pointerUp()
+        rule.waitForIdle()
+        assertThat(state.transitionState).isIdle()
+        assertThat(state.transitionState).hasCurrentScene(SceneB)
+    }
+
+    @Test
+    fun customizeStlNestedScrollBehavior_EdgeAlways() {
+        var canScroll = true
+        val state = setup2ScenesAndScrollTouchSlop {
+            Modifier.verticalNestedScrollToScene(bottomBehavior = NestedScrollBehavior.EdgeAlways)
+                .scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical)
+        }
+
+        scrollUp(percent = 0.5f)
+        assertThat(state.transitionState).isIdle()
+
+        // Reach the end of the scrollable element
+        canScroll = false
+        scrollUp(percent = 0.5f)
+        val transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasProgress(0.5f)
+
+        pointerUp()
+        rule.waitForIdle()
+        assertThat(state.transitionState).isIdle()
+        assertThat(state.transitionState).hasCurrentScene(SceneB)
+    }
+
+    @Test
+    fun customizeStlNestedScrollBehavior_multipleRequests() {
+        val state = setup2ScenesAndScrollTouchSlop {
+            Modifier
+                // This verticalNestedScrollToScene is closer the STL (an ancestor node)
+                .verticalNestedScrollToScene(bottomBehavior = NestedScrollBehavior.EdgeAlways)
+                // Another verticalNestedScrollToScene modifier
+                .verticalNestedScrollToScene(
+                    bottomBehavior = NestedScrollBehavior.DuringTransitionBetweenScenes
+                )
+                .scrollable(rememberScrollableState { 0f }, Vertical)
+        }
+
+        scrollUp(percent = 0.5f)
+        // EdgeAlways always consume the remaining scroll, DuringTransitionBetweenScenes does not.
+        val transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasProgress(0.5f)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt
index be0d899..9e69601 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt
@@ -18,8 +18,11 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
 import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -74,9 +77,9 @@
 
     @Test
     @DisableSceneContainer
+    @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun alpha_WhenNotGone_clockMigrationFlagIsOff_emitsKeyguardAlpha() =
         testScope.runTest {
-            mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
             val alpha by collectLastValue(underTest.alpha)
 
             keyguardTransitionRepository.sendTransitionSteps(
@@ -186,9 +189,9 @@
 
     @Test
     @DisableSceneContainer
+    @EnableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun alpha_whenGone_equalsZero() =
         testScope.runTest {
-            mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
             val alpha by collectLastValue(underTest.alpha)
 
             keyguardTransitionRepository.sendTransitionStep(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 63d06a4..41c5b73 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -18,6 +18,8 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags as AConfigFlags
@@ -69,10 +71,11 @@
     private val burnInFlow = MutableStateFlow(BurnInModel())
 
     @Before
+    @DisableFlags(
+        AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
+        AConfigFlags.FLAG_COMPOSE_LOCKSCREEN
+    )
     fun setUp() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
-
         MockitoAnnotations.initMocks(this)
         whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow)
         kosmos.burnInInteractor = burnInInteractor
@@ -174,10 +177,9 @@
         }
 
     @Test
+    @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun translationAndScale_whenFullyDozing_MigrationFlagOff_staysOutOfTopInset() =
         testScope.runTest {
-            mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-
             burnInParameters =
                 burnInParameters.copy(
                     minViewY = 100,
@@ -226,10 +228,9 @@
         }
 
     @Test
+    @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun translationAndScale_whenFullyDozing_MigrationFlagOn_staysOutOfTopInset() =
         testScope.runTest {
-            mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-
             burnInParameters =
                 burnInParameters.copy(
                     minViewY = 100,
@@ -310,104 +311,99 @@
         }
 
     @Test
+    @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
+    @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun translationAndScale_composeFlagOff_weatherLargeClock() =
         testBurnInViewModelForClocks(
             isSmallClock = false,
             isWeatherClock = true,
             expectedScaleOnly = false,
-            enableMigrateClocksToBlueprintFlag = true,
-            enableComposeLockscreenFlag = false
         )
 
     @Test
+    @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
+    @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun translationAndScale_composeFlagOff_weatherSmallClock() =
         testBurnInViewModelForClocks(
             isSmallClock = true,
             isWeatherClock = true,
             expectedScaleOnly = false,
-            enableMigrateClocksToBlueprintFlag = true,
-            enableComposeLockscreenFlag = false
         )
 
     @Test
+    @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
+    @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun translationAndScale_composeFlagOff_nonWeatherLargeClock() =
         testBurnInViewModelForClocks(
             isSmallClock = false,
             isWeatherClock = false,
             expectedScaleOnly = true,
-            enableMigrateClocksToBlueprintFlag = true,
-            enableComposeLockscreenFlag = false
         )
 
     @Test
+    @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
+    @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun translationAndScale_composeFlagOff_nonWeatherSmallClock() =
         testBurnInViewModelForClocks(
             isSmallClock = true,
             isWeatherClock = false,
             expectedScaleOnly = false,
-            enableMigrateClocksToBlueprintFlag = true,
-            enableComposeLockscreenFlag = false
         )
 
     @Test
+    @EnableFlags(
+        AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
+        AConfigFlags.FLAG_COMPOSE_LOCKSCREEN
+    )
     fun translationAndScale_composeFlagOn_weatherLargeClock() =
         testBurnInViewModelForClocks(
             isSmallClock = false,
             isWeatherClock = true,
             expectedScaleOnly = false,
-            enableMigrateClocksToBlueprintFlag = true,
-            enableComposeLockscreenFlag = true
         )
 
     @Test
+    @EnableFlags(
+        AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
+        AConfigFlags.FLAG_COMPOSE_LOCKSCREEN
+    )
     fun translationAndScale_composeFlagOn_weatherSmallClock() =
         testBurnInViewModelForClocks(
             isSmallClock = true,
             isWeatherClock = true,
             expectedScaleOnly = false,
-            enableMigrateClocksToBlueprintFlag = true,
-            enableComposeLockscreenFlag = true
         )
 
     @Test
+    @EnableFlags(
+        AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
+        AConfigFlags.FLAG_COMPOSE_LOCKSCREEN
+    )
     fun translationAndScale_composeFlagOn_nonWeatherLargeClock() =
         testBurnInViewModelForClocks(
             isSmallClock = false,
             isWeatherClock = false,
             expectedScaleOnly = true,
-            enableMigrateClocksToBlueprintFlag = true,
-            enableComposeLockscreenFlag = true
         )
 
     @Test
+    @EnableFlags(
+        AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
+        AConfigFlags.FLAG_COMPOSE_LOCKSCREEN
+    )
     fun translationAndScale_composeFlagOn_nonWeatherSmallClock() =
         testBurnInViewModelForClocks(
             isSmallClock = true,
             isWeatherClock = false,
             expectedScaleOnly = false,
-            enableMigrateClocksToBlueprintFlag = true,
-            enableComposeLockscreenFlag = true
         )
 
     private fun testBurnInViewModelForClocks(
         isSmallClock: Boolean,
         isWeatherClock: Boolean,
         expectedScaleOnly: Boolean,
-        enableMigrateClocksToBlueprintFlag: Boolean,
-        enableComposeLockscreenFlag: Boolean
     ) =
         testScope.runTest {
-            if (enableMigrateClocksToBlueprintFlag) {
-                mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-            } else {
-                mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-            }
-
-            if (enableComposeLockscreenFlag) {
-                mSetFlagsRule.enableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
-            } else {
-                mSetFlagsRule.disableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
-            }
             if (isSmallClock) {
                 keyguardClockRepository.setClockSize(ClockSize.SMALL)
                 // we need the following step to update stateFlow value
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 10d1891..0f61233 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -34,6 +34,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
+import com.android.systemui.Flags;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.res.R;
@@ -130,7 +131,10 @@
                     verifyPasswordAndUnlock();
                 }
             });
-            okButton.setOnHoverListener(mLiftToActivateListener);
+
+            if (!Flags.simPinTalkbackFixForDoubleSubmit()) {
+                okButton.setOnHoverListener(mLiftToActivateListener);
+            }
         }
         if (pinInputFieldStyledFocusState()) {
             collectFlow(mPasswordEntry, mKeyguardKeyboardInteractor.isAnyKeyboardConnected(),
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
index 1dbd500..c4abcd2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
@@ -54,6 +54,7 @@
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.view.inputmethod.Flags;
 import android.widget.FrameLayout;
 
 import androidx.annotation.Nullable;
@@ -285,8 +286,11 @@
 
         // Set up the context group of buttons
         mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
+        final int switcherResId = Flags.imeSwitcherRevamp()
+                ? com.android.internal.R.drawable.ic_ime_switcher_new
+                : R.drawable.ic_ime_switcher_default;
         final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
-                mLightContext, R.drawable.ic_ime_switcher_default);
+                mLightContext, switcherResId);
         final ContextualButton accessibilityButton =
                 new ContextualButton(R.id.accessibility_button, mLightContext,
                         R.drawable.ic_sysbar_accessibility_button);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 11ccdff..59fd0ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -57,7 +57,7 @@
         interactor.ongoingCallState
             .map { state ->
                 when (state) {
-                    is OngoingCallModel.NoCall -> OngoingActivityChipModel.Hidden
+                    is OngoingCallModel.NoCall -> OngoingActivityChipModel.Hidden()
                     is OngoingCallModel.InCall -> {
                         // This block mimics OngoingCallController#updateChip.
                         if (state.startTimeMs <= 0L) {
@@ -82,7 +82,7 @@
                     }
                 }
             }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden())
 
     private fun getOnClickListener(state: OngoingCallModel.InCall): View.OnClickListener? {
         if (state.intent == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
index bafec38..6ea72b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
@@ -44,9 +44,10 @@
             // No custom on-click, because the dialog will automatically be dismissed when the
             // button is clicked anyway.
             setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
-            setPositiveButton(R.string.cast_to_other_device_stop_dialog_button) { _, _ ->
-                stopAction.invoke()
-            }
+            setPositiveButton(
+                R.string.cast_to_other_device_stop_dialog_button,
+                endMediaProjectionDialogHelper.wrapStopAction(stopAction),
+            )
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
index 7dc9b25..b0c8321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
@@ -55,9 +55,10 @@
             // No custom on-click, because the dialog will automatically be dismissed when the
             // button is clicked anyway.
             setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
-            setPositiveButton(R.string.cast_to_other_device_stop_dialog_button) { _, _ ->
-                stopAction.invoke()
-            }
+            setPositiveButton(
+                R.string.cast_to_other_device_stop_dialog_button,
+                endMediaProjectionDialogHelper.wrapStopAction(stopAction),
+            )
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index afa9cce..d9b0504 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -18,6 +18,9 @@
 
 import android.content.Context
 import androidx.annotation.DrawableRes
+import com.android.internal.jank.Cuj
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
@@ -35,6 +38,7 @@
 import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
 import com.android.systemui.util.time.SystemClock
@@ -60,6 +64,7 @@
     private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
     private val mediaRouterChipInteractor: MediaRouterChipInteractor,
     private val systemClock: SystemClock,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
     @StatusBarChipsLog private val logger: LogBuffer,
 ) : OngoingActivityChipViewModel {
@@ -74,18 +79,18 @@
         mediaProjectionChipInteractor.projection
             .map { projectionModel ->
                 when (projectionModel) {
-                    is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden
+                    is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden()
                     is ProjectionChipModel.Projecting -> {
                         if (projectionModel.type != ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE) {
-                            OngoingActivityChipModel.Hidden
+                            OngoingActivityChipModel.Hidden()
                         } else {
                             createCastScreenToOtherDeviceChip(projectionModel)
                         }
                     }
                 }
             }
-            // See b/347726238.
-            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
+            // See b/347726238 for [SharingStarted.Lazily] reasoning.
+            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())
 
     /**
      * The cast chip to show, based only on MediaRouter API events.
@@ -109,7 +114,7 @@
         mediaRouterChipInteractor.mediaRouterCastingState
             .map { routerModel ->
                 when (routerModel) {
-                    is MediaRouterCastModel.DoingNothing -> OngoingActivityChipModel.Hidden
+                    is MediaRouterCastModel.DoingNothing -> OngoingActivityChipModel.Hidden()
                     is MediaRouterCastModel.Casting -> {
                         // A consequence of b/269975671 is that MediaRouter will mark a device as
                         // casting before casting has actually started. To alleviate this bug a bit,
@@ -123,9 +128,9 @@
                     }
                 }
             }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden())
 
-    override val chip: StateFlow<OngoingActivityChipModel> =
+    private val internalChip: StateFlow<OngoingActivityChipModel> =
         combine(projectionChip, routerChip) { projection, router ->
                 logger.log(
                     TAG,
@@ -159,17 +164,24 @@
                     router
                 }
             }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden())
+
+    private val hideChipDuringDialogTransitionHelper = ChipTransitionHelper(scope)
+
+    override val chip: StateFlow<OngoingActivityChipModel> =
+        hideChipDuringDialogTransitionHelper.createChipFlow(internalChip)
 
     /** Stops the currently active projection. */
-    private fun stopProjecting() {
-        logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (projection)" })
+    private fun stopProjectingFromDialog() {
+        logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested from dialog (projection)" })
+        hideChipDuringDialogTransitionHelper.onActivityStoppedFromDialog()
         mediaProjectionChipInteractor.stopProjecting()
     }
 
     /** Stops the currently active media route. */
-    private fun stopMediaRouterCasting() {
-        logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (router)" })
+    private fun stopMediaRouterCastingFromDialog() {
+        logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested from dialog (router)" })
+        hideChipDuringDialogTransitionHelper.onActivityStoppedFromDialog()
         mediaRouterChipInteractor.stopCasting()
     }
 
@@ -190,6 +202,8 @@
             startTimeMs = systemClock.elapsedRealtime(),
             createDialogLaunchOnClickListener(
                 createCastScreenToOtherDeviceDialogDelegate(state),
+                dialogTransitionAnimator,
+                DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Cast to other device"),
                 logger,
                 TAG,
             ),
@@ -207,6 +221,11 @@
             colors = ColorsModel.Red,
             createDialogLaunchOnClickListener(
                 createGenericCastToOtherDeviceDialogDelegate(deviceName),
+                dialogTransitionAnimator,
+                DialogCuj(
+                    Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
+                    tag = "Cast to other device audio only",
+                ),
                 logger,
                 TAG,
             ),
@@ -219,7 +238,7 @@
         EndCastScreenToOtherDeviceDialogDelegate(
             endMediaProjectionDialogHelper,
             context,
-            stopAction = this::stopProjecting,
+            stopAction = this::stopProjectingFromDialog,
             state,
         )
 
@@ -228,7 +247,7 @@
             endMediaProjectionDialogHelper,
             context,
             deviceName,
-            stopAction = this::stopMediaRouterCasting,
+            stopAction = this::stopMediaRouterCastingFromDialog,
         )
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
index 6004365..2d9ccb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
@@ -17,7 +17,9 @@
 package com.android.systemui.statusbar.chips.mediaprojection.ui.view
 
 import android.app.ActivityManager
+import android.content.DialogInterface
 import android.content.pm.PackageManager
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -29,6 +31,7 @@
 @Inject
 constructor(
     private val dialogFactory: SystemUIDialog.Factory,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val packageManager: PackageManager,
 ) {
     /** Creates a new [SystemUIDialog] using the given delegate. */
@@ -36,6 +39,28 @@
         return dialogFactory.create(delegate)
     }
 
+    /**
+     * Returns the click listener that should be invoked if a user clicks "Stop" on the end media
+     * projection dialog.
+     *
+     * The click listener will invoke [stopAction] and also do some UI manipulation.
+     *
+     * @param stopAction an action that, when invoked, should notify system API(s) that the media
+     *   projection should be stopped.
+     */
+    fun wrapStopAction(stopAction: () -> Unit): DialogInterface.OnClickListener {
+        return DialogInterface.OnClickListener { _, _ ->
+            // If the projection is stopped, then the chip will disappear, so we don't want the
+            // dialog to animate back into the chip just for the chip to disappear in a few frames.
+            dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
+            stopAction.invoke()
+            // TODO(b/332662551): If the projection is stopped, there's a brief moment where the
+            // dialog closes and the chip re-shows because the system APIs haven't come back and
+            // told SysUI that the projection has officially stopped. It would be great for the chip
+            // to not re-show at all.
+        }
+    }
+
     fun getAppName(state: MediaProjectionState.Projecting): CharSequence? {
         val specificTaskInfo =
             if (state is MediaProjectionState.Projecting.SingleTask) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt
index 1eca827..72656ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt
@@ -52,9 +52,10 @@
             // No custom on-click, because the dialog will automatically be dismissed when the
             // button is clicked anyway.
             setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
-            setPositiveButton(R.string.screenrecord_stop_dialog_button) { _, _ ->
-                stopAction.invoke()
-            }
+            setPositiveButton(
+                R.string.screenrecord_stop_dialog_button,
+                endMediaProjectionDialogHelper.wrapStopAction(stopAction),
+            )
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index 0c34981..fcf3de4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -19,6 +19,9 @@
 import android.app.ActivityManager
 import android.content.Context
 import androidx.annotation.DrawableRes
+import com.android.internal.jank.Cuj
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
@@ -32,8 +35,10 @@
 import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
 import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel
 import com.android.systemui.statusbar.chips.screenrecord.ui.view.EndScreenRecordingDialogDelegate
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
 import com.android.systemui.util.time.SystemClock
@@ -52,15 +57,18 @@
     @Application private val scope: CoroutineScope,
     private val context: Context,
     private val interactor: ScreenRecordChipInteractor,
+    private val shareToAppChipViewModel: ShareToAppChipViewModel,
     private val systemClock: SystemClock,
     private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
     @StatusBarChipsLog private val logger: LogBuffer,
 ) : OngoingActivityChipViewModel {
-    override val chip: StateFlow<OngoingActivityChipModel> =
+
+    private val internalChip =
         interactor.screenRecordState
             .map { state ->
                 when (state) {
-                    is ScreenRecordChipModel.DoingNothing -> OngoingActivityChipModel.Hidden
+                    is ScreenRecordChipModel.DoingNothing -> OngoingActivityChipModel.Hidden()
                     is ScreenRecordChipModel.Starting -> {
                         OngoingActivityChipModel.Shown.Countdown(
                             colors = ColorsModel.Red,
@@ -80,6 +88,11 @@
                             startTimeMs = systemClock.elapsedRealtime(),
                             createDialogLaunchOnClickListener(
                                 createDelegate(state.recordedTask),
+                                dialogTransitionAnimator,
+                                DialogCuj(
+                                    Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
+                                    tag = "Screen record",
+                                ),
                                 logger,
                                 TAG,
                             ),
@@ -87,8 +100,13 @@
                     }
                 }
             }
-            // See b/347726238.
-            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
+            // See b/347726238 for [SharingStarted.Lazily] reasoning.
+            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())
+
+    private val chipTransitionHelper = ChipTransitionHelper(scope)
+
+    override val chip: StateFlow<OngoingActivityChipModel> =
+        chipTransitionHelper.createChipFlow(internalChip)
 
     private fun createDelegate(
         recordedTask: ActivityManager.RunningTaskInfo?
@@ -96,13 +114,15 @@
         return EndScreenRecordingDialogDelegate(
             endMediaProjectionDialogHelper,
             context,
-            stopAction = this::stopRecording,
+            stopAction = this::stopRecordingFromDialog,
             recordedTask,
         )
     }
 
-    private fun stopRecording() {
-        logger.log(TAG, LogLevel.INFO, {}, { "Stop recording requested" })
+    private fun stopRecordingFromDialog() {
+        logger.log(TAG, LogLevel.INFO, {}, { "Stop recording requested from dialog" })
+        chipTransitionHelper.onActivityStoppedFromDialog()
+        shareToAppChipViewModel.onRecordingStoppedFromDialog()
         interactor.stopRecording()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt
index 564f20e..d10bd77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt
@@ -44,9 +44,10 @@
             // No custom on-click, because the dialog will automatically be dismissed when the
             // button is clicked anyway.
             setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
-            setPositiveButton(R.string.share_to_app_stop_dialog_button) { _, _ ->
-                stopAction.invoke()
-            }
+            setPositiveButton(
+                R.string.share_to_app_stop_dialog_button,
+                endMediaProjectionDialogHelper.wrapStopAction(stopAction),
+            )
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index ddebd3a..85973fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -18,6 +18,9 @@
 
 import android.content.Context
 import androidx.annotation.DrawableRes
+import com.android.internal.jank.Cuj
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
@@ -32,6 +35,7 @@
 import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareToAppDialogDelegate
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
 import com.android.systemui.util.time.SystemClock
@@ -55,28 +59,49 @@
     private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
     private val systemClock: SystemClock,
     private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
     @StatusBarChipsLog private val logger: LogBuffer,
 ) : OngoingActivityChipViewModel {
-    override val chip: StateFlow<OngoingActivityChipModel> =
+    private val internalChip =
         mediaProjectionChipInteractor.projection
             .map { projectionModel ->
                 when (projectionModel) {
-                    is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden
+                    is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden()
                     is ProjectionChipModel.Projecting -> {
                         if (projectionModel.type != ProjectionChipModel.Type.SHARE_TO_APP) {
-                            OngoingActivityChipModel.Hidden
+                            OngoingActivityChipModel.Hidden()
                         } else {
                             createShareToAppChip(projectionModel)
                         }
                     }
                 }
             }
-            // See b/347726238.
-            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
+            // See b/347726238 for [SharingStarted.Lazily] reasoning.
+            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())
+
+    private val chipTransitionHelper = ChipTransitionHelper(scope)
+
+    override val chip: StateFlow<OngoingActivityChipModel> =
+        chipTransitionHelper.createChipFlow(internalChip)
+
+    /**
+     * Notifies this class that the user just stopped a screen recording from the dialog that's
+     * shown when you tap the recording chip.
+     */
+    fun onRecordingStoppedFromDialog() {
+        // When a screen recording is active, share-to-app is also active (screen recording is just
+        // a special case of share-to-app, where the specific app receiving the share is System UI).
+        // When a screen recording is stopped, we immediately hide the screen recording chip in
+        // [com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel].
+        // We *also* need to immediately hide the share-to-app chip so it doesn't briefly show.
+        // See b/350891338.
+        chipTransitionHelper.onActivityStoppedFromDialog()
+    }
 
     /** Stops the currently active projection. */
-    private fun stopProjecting() {
-        logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested" })
+    private fun stopProjectingFromDialog() {
+        logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested from dialog" })
+        chipTransitionHelper.onActivityStoppedFromDialog()
         mediaProjectionChipInteractor.stopProjecting()
     }
 
@@ -92,7 +117,16 @@
             colors = ColorsModel.Red,
             // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
             startTimeMs = systemClock.elapsedRealtime(),
-            createDialogLaunchOnClickListener(createShareToAppDialogDelegate(state), logger, TAG),
+            createDialogLaunchOnClickListener(
+                createShareToAppDialogDelegate(state),
+                dialogTransitionAnimator,
+                DialogCuj(
+                    Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
+                    tag = "Share to app",
+                ),
+                logger,
+                TAG,
+            ),
         )
     }
 
@@ -100,7 +134,7 @@
         EndShareToAppDialogDelegate(
             endMediaProjectionDialogHelper,
             context,
-            stopAction = this::stopProjecting,
+            stopAction = this::stopProjectingFromDialog,
             state,
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index 40f86f9..17cf60b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -24,9 +24,15 @@
     /** Condensed name representing the model, used for logs. */
     abstract val logName: String
 
-    /** This chip shouldn't be shown. */
-    data object Hidden : OngoingActivityChipModel() {
-        override val logName = "Hidden"
+    /**
+     * This chip shouldn't be shown.
+     *
+     * @property shouldAnimate true if the transition from [Shown] to [Hidden] should be animated,
+     *   and false if that transition should *not* be animated (i.e. the chip view should
+     *   immediately disappear).
+     */
+    data class Hidden(val shouldAnimate: Boolean = true) : OngoingActivityChipModel() {
+        override val logName = "Hidden(anim=$shouldAnimate)"
     }
 
     /** This chip should be shown with the given information. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelper.kt
new file mode 100644
index 0000000..92e72c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelper.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ui.viewmodel
+
+import android.annotation.SuppressLint
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformLatest
+import kotlinx.coroutines.launch
+
+/**
+ * A class that can help [OngoingActivityChipViewModel] instances with various transition states.
+ *
+ * For now, this class's only functionality is immediately hiding the chip if the user has tapped an
+ * activity chip and then clicked "Stop" on the resulting dialog. There's a bit of a delay between
+ * when the user clicks "Stop" and when the system services notify SysUI that the activity has
+ * indeed stopped. We don't want the chip to briefly show for a few frames during that delay, so
+ * this class helps us immediately hide the chip as soon as the user clicks "Stop" in the dialog.
+ * See b/353249803#comment4.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class ChipTransitionHelper(@Application private val scope: CoroutineScope) {
+    /** A flow that emits each time the user has clicked "Stop" on the dialog. */
+    @SuppressLint("SharedFlowCreation")
+    private val activityStoppedFromDialogEvent = MutableSharedFlow<Unit>()
+
+    /** True if the user recently stopped the activity from the dialog. */
+    private val wasActivityRecentlyStoppedFromDialog: Flow<Boolean> =
+        activityStoppedFromDialogEvent
+            .transformLatest {
+                // Give system services 500ms to stop the activity and notify SysUI. Once more than
+                // 500ms has elapsed, we should go back to using the current system service
+                // information as the source of truth.
+                emit(true)
+                delay(500)
+                emit(false)
+            }
+            // Use stateIn so that the flow created in [createChipFlow] is guaranteed to
+            // emit. (`combine`s require that all input flows have emitted.)
+            .stateIn(scope, SharingStarted.Lazily, false)
+
+    /**
+     * Notifies this class that the user just clicked "Stop" on the stop dialog that's shown when
+     * the chip is tapped.
+     *
+     * Call this method in order to immediately hide the chip.
+     */
+    fun onActivityStoppedFromDialog() {
+        // Because this event causes UI changes, make sure it's launched on the main thread scope.
+        scope.launch { activityStoppedFromDialogEvent.emit(Unit) }
+    }
+
+    /**
+     * Creates a flow that will forcibly hide the chip if the user recently stopped the activity
+     * (see [onActivityStoppedFromDialog]). In general, this flow just uses value in [chip].
+     */
+    fun createChipFlow(chip: Flow<OngoingActivityChipModel>): StateFlow<OngoingActivityChipModel> {
+        return combine(
+                chip,
+                wasActivityRecentlyStoppedFromDialog,
+            ) { chipModel, activityRecentlyStopped ->
+                if (activityRecentlyStopped) {
+                    // There's a bit of a delay between when the user stops an activity via
+                    // SysUI and when the system services notify SysUI that the activity has
+                    // indeed stopped. Prevent the chip from showing during this delay by
+                    // immediately hiding it without any animation.
+                    OngoingActivityChipModel.Hidden(shouldAnimate = false)
+                } else {
+                    chipModel
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden())
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
index ee010f7..2fc366b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
@@ -17,10 +17,14 @@
 package com.android.systemui.statusbar.chips.ui.viewmodel
 
 import android.view.View
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import kotlinx.coroutines.flow.StateFlow
 
@@ -36,13 +40,19 @@
         /** Creates a chip click listener that launches a dialog created by [dialogDelegate]. */
         fun createDialogLaunchOnClickListener(
             dialogDelegate: SystemUIDialog.Delegate,
+            dialogTransitionAnimator: DialogTransitionAnimator,
+            cuj: DialogCuj,
             @StatusBarChipsLog logger: LogBuffer,
             tag: String,
         ): View.OnClickListener {
-            return View.OnClickListener { _ ->
+            return View.OnClickListener { view ->
                 logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" })
                 val dialog = dialogDelegate.createDialog()
-                dialog.show()
+                val launchableView =
+                    view.requireViewById<ChipBackgroundContainer>(
+                        R.id.ongoing_activity_chip_background
+                    )
+                dialogTransitionAnimator.showFromView(dialog, launchableView, cuj)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index 15c348e..b0d897d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -26,11 +26,14 @@
 import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel
 import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 /**
@@ -50,49 +53,132 @@
     callChipViewModel: CallChipViewModel,
     @StatusBarChipsLog private val logger: LogBuffer,
 ) {
+    private enum class ChipType {
+        ScreenRecord,
+        ShareToApp,
+        CastToOtherDevice,
+        Call,
+    }
+
+    /** Model that helps us internally track the various chip states from each of the types. */
+    private sealed interface InternalChipModel {
+        /**
+         * Represents that we've internally decided to show the chip with type [type] with the given
+         * [model] information.
+         */
+        data class Shown(val type: ChipType, val model: OngoingActivityChipModel.Shown) :
+            InternalChipModel
+
+        /**
+         * Represents that all chip types would like to be hidden. Each value specifies *how* that
+         * chip type should get hidden.
+         */
+        data class Hidden(
+            val screenRecord: OngoingActivityChipModel.Hidden,
+            val shareToApp: OngoingActivityChipModel.Hidden,
+            val castToOtherDevice: OngoingActivityChipModel.Hidden,
+            val call: OngoingActivityChipModel.Hidden,
+        ) : InternalChipModel
+    }
+
+    private val internalChip: Flow<InternalChipModel> =
+        combine(
+            screenRecordChipViewModel.chip,
+            shareToAppChipViewModel.chip,
+            castToOtherDeviceChipViewModel.chip,
+            callChipViewModel.chip,
+        ) { screenRecord, shareToApp, castToOtherDevice, call ->
+            logger.log(
+                TAG,
+                LogLevel.INFO,
+                {
+                    str1 = screenRecord.logName
+                    str2 = shareToApp.logName
+                    str3 = castToOtherDevice.logName
+                },
+                { "Chips: ScreenRecord=$str1 > ShareToApp=$str2 > CastToOther=$str3..." },
+            )
+            logger.log(TAG, LogLevel.INFO, { str1 = call.logName }, { "... > Call=$str1" })
+            // This `when` statement shows the priority order of the chips.
+            when {
+                // Screen recording also activates the media projection APIs, so whenever the
+                // screen recording chip is active, the media projection chip would also be
+                // active. We want the screen-recording-specific chip shown in this case, so we
+                // give the screen recording chip priority. See b/296461748.
+                screenRecord is OngoingActivityChipModel.Shown ->
+                    InternalChipModel.Shown(ChipType.ScreenRecord, screenRecord)
+                shareToApp is OngoingActivityChipModel.Shown ->
+                    InternalChipModel.Shown(ChipType.ShareToApp, shareToApp)
+                castToOtherDevice is OngoingActivityChipModel.Shown ->
+                    InternalChipModel.Shown(ChipType.CastToOtherDevice, castToOtherDevice)
+                call is OngoingActivityChipModel.Shown ->
+                    InternalChipModel.Shown(ChipType.Call, call)
+                else -> {
+                    // We should only get here if all chip types are hidden
+                    check(screenRecord is OngoingActivityChipModel.Hidden)
+                    check(shareToApp is OngoingActivityChipModel.Hidden)
+                    check(castToOtherDevice is OngoingActivityChipModel.Hidden)
+                    check(call is OngoingActivityChipModel.Hidden)
+                    InternalChipModel.Hidden(
+                        screenRecord = screenRecord,
+                        shareToApp = shareToApp,
+                        castToOtherDevice = castToOtherDevice,
+                        call = call,
+                    )
+                }
+            }
+        }
+
     /**
      * A flow modeling the chip that should be shown in the status bar after accounting for possibly
-     * multiple ongoing activities.
+     * multiple ongoing activities and animation requirements.
      *
      * [com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment] is responsible for
      * actually displaying the chip.
      */
     val chip: StateFlow<OngoingActivityChipModel> =
-        combine(
-                screenRecordChipViewModel.chip,
-                shareToAppChipViewModel.chip,
-                castToOtherDeviceChipViewModel.chip,
-                callChipViewModel.chip,
-            ) { screenRecord, shareToApp, castToOtherDevice, call ->
-                logger.log(
-                    TAG,
-                    LogLevel.INFO,
-                    {
-                        str1 = screenRecord.logName
-                        str2 = shareToApp.logName
-                        str3 = castToOtherDevice.logName
-                    },
-                    { "Chips: ScreenRecord=$str1 > ShareToApp=$str2 > CastToOther=$str3..." },
-                )
-                logger.log(TAG, LogLevel.INFO, { str1 = call.logName }, { "... > Call=$str1" })
-                // This `when` statement shows the priority order of the chips
-                when {
-                    // Screen recording also activates the media projection APIs, so whenever the
-                    // screen recording chip is active, the media projection chip would also be
-                    // active. We want the screen-recording-specific chip shown in this case, so we
-                    // give the screen recording chip priority. See b/296461748.
-                    screenRecord is OngoingActivityChipModel.Shown -> screenRecord
-                    shareToApp is OngoingActivityChipModel.Shown -> shareToApp
-                    castToOtherDevice is OngoingActivityChipModel.Shown -> castToOtherDevice
-                    else -> call
+        internalChip
+            .pairwise(initialValue = DEFAULT_INTERNAL_HIDDEN_MODEL)
+            .map { (old, new) ->
+                if (old is InternalChipModel.Shown && new is InternalChipModel.Hidden) {
+                    // If we're transitioning from showing the chip to hiding the chip, different
+                    // chips require different animation behaviors. For example, the screen share
+                    // chips shouldn't animate if the user stopped the screen share from the dialog
+                    // (see b/353249803#comment4), but the call chip should always animate.
+                    //
+                    // This `when` block makes sure that when we're transitioning from Shown to
+                    // Hidden, we check what chip type was previously showing and we use that chip
+                    // type's hide animation behavior.
+                    when (old.type) {
+                        ChipType.ScreenRecord -> new.screenRecord
+                        ChipType.ShareToApp -> new.shareToApp
+                        ChipType.CastToOtherDevice -> new.castToOtherDevice
+                        ChipType.Call -> new.call
+                    }
+                } else if (new is InternalChipModel.Shown) {
+                    // If we have a chip to show, always show it.
+                    new.model
+                } else {
+                    // In the Hidden -> Hidden transition, it shouldn't matter which hidden model we
+                    // choose because no animation should happen regardless.
+                    OngoingActivityChipModel.Hidden()
                 }
             }
             // Some of the chips could have timers in them and we don't want the start time
             // for those timers to get reset for any reason. So, as soon as any subscriber has
-            // requested the chip information, we need to maintain it forever. See b/347726238.
-            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
+            // requested the chip information, we maintain it forever by using
+            // [SharingStarted.Lazily]. See b/347726238.
+            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())
 
     companion object {
         private const val TAG = "ChipsViewModel"
+
+        private val DEFAULT_INTERNAL_HIDDEN_MODEL =
+            InternalChipModel.Hidden(
+                screenRecord = OngoingActivityChipModel.Hidden(),
+                shareToApp = OngoingActivityChipModel.Hidden(),
+                castToOtherDevice = OngoingActivityChipModel.Hidden(),
+                call = OngoingActivityChipModel.Hidden(),
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index aced0be..0320a7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -528,9 +528,10 @@
                 }
 
                 @Override
-                public void onOngoingActivityStatusChanged(boolean hasOngoingActivity) {
+                public void onOngoingActivityStatusChanged(
+                        boolean hasOngoingActivity, boolean shouldAnimate) {
                     mHasOngoingActivity = hasOngoingActivity;
-                    updateStatusBarVisibilities(/* animate= */ true);
+                    updateStatusBarVisibilities(shouldAnimate);
                 }
 
                 @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index ae1898b..4c97854 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -122,7 +122,8 @@
 
                                     // Notify listeners
                                     listener.onOngoingActivityStatusChanged(
-                                        hasOngoingActivity = true
+                                        hasOngoingActivity = true,
+                                        shouldAnimate = true,
                                     )
                                 }
                                 is OngoingActivityChipModel.Hidden -> {
@@ -130,7 +131,8 @@
                                     // b/192243808 and [Chronometer.start].
                                     chipTimeView.stop()
                                     listener.onOngoingActivityStatusChanged(
-                                        hasOngoingActivity = false
+                                        hasOngoingActivity = false,
+                                        shouldAnimate = chipModel.shouldAnimate,
                                     )
                                 }
                             }
@@ -266,8 +268,13 @@
     /** Called when a transition from lockscreen to dream has started. */
     fun onTransitionFromLockscreenToDreamStarted()
 
-    /** Called when the status of the ongoing activity chip (active or not active) has changed. */
-    fun onOngoingActivityStatusChanged(hasOngoingActivity: Boolean)
+    /**
+     * Called when the status of the ongoing activity chip (active or not active) has changed.
+     *
+     * @param shouldAnimate true if the chip should animate in/out, and false if the chip should
+     *   immediately appear/disappear.
+     */
+    fun onOngoingActivityStatusChanged(hasOngoingActivity: Boolean, shouldAnimate: Boolean)
 
     /**
      * Called when the scene state has changed such that the home status bar is newly allowed or no
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 79933ee..a94ef36 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -16,6 +16,7 @@
 package com.android.keyguard
 
 import android.content.BroadcastReceiver
+import android.platform.test.annotations.DisableFlags
 import android.view.View
 import android.view.ViewTreeObserver
 import android.widget.FrameLayout
@@ -263,9 +264,9 @@
         }
 
     @Test
+    @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun keyguardCallback_visibilityChanged_clockDozeCalled() =
         runBlocking(IMMEDIATE) {
-            mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
             val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
             verify(keyguardUpdateMonitor).registerCallback(capture(captor))
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 1d78168..892375d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -29,6 +29,7 @@
 
 import android.database.ContentObserver;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
 import android.provider.Settings;
 import android.view.View;
 
@@ -48,11 +49,10 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
 public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchControllerBaseTest {
     @Test
     public void testInit_viewAlreadyAttached() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         mController.init();
 
         verifyAttachment(times(1));
@@ -60,8 +60,6 @@
 
     @Test
     public void testInit_viewNotYetAttached() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
 
@@ -78,16 +76,12 @@
 
     @Test
     public void testInitSubControllers() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         mController.init();
         verify(mKeyguardSliceViewController).init();
     }
 
     @Test
     public void testInit_viewDetached() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
         mController.init();
@@ -101,8 +95,6 @@
 
     @Test
     public void testPluginPassesStatusBarState() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
 
@@ -116,8 +108,6 @@
 
     @Test
     public void testSmartspaceEnabledRemovesKeyguardStatusArea() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         when(mSmartspaceController.isEnabled()).thenReturn(true);
         mController.init();
 
@@ -126,8 +116,6 @@
 
     @Test
     public void onLocaleListChangedRebuildsSmartspaceView() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         when(mSmartspaceController.isEnabled()).thenReturn(true);
         mController.init();
 
@@ -138,8 +126,6 @@
 
     @Test
     public void onLocaleListChanged_rebuildsSmartspaceViews_whenDecouplingEnabled() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         when(mSmartspaceController.isEnabled()).thenReturn(true);
         when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true);
         mController.init();
@@ -153,8 +139,6 @@
 
     @Test
     public void testSmartspaceDisabledShowsKeyguardStatusArea() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         when(mSmartspaceController.isEnabled()).thenReturn(false);
         mController.init();
 
@@ -163,8 +147,6 @@
 
     @Test
     public void testRefresh() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         mController.refresh();
 
         verify(mSmartspaceController).requestSmartspaceUpdate();
@@ -172,8 +154,6 @@
 
     @Test
     public void testChangeToDoubleLineClockSetsSmallClock() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         when(mSecureSettings.getIntForUser(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1,
                 UserHandle.USER_CURRENT))
                 .thenReturn(0);
@@ -197,15 +177,11 @@
 
     @Test
     public void testGetClock_ForwardsToClock() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         assertEquals(mClockController, mController.getClock());
     }
 
     @Test
     public void testGetLargeClockBottom_returnsExpectedValue() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         when(mLargeClockFrame.getVisibility()).thenReturn(View.VISIBLE);
         when(mLargeClockFrame.getHeight()).thenReturn(100);
         when(mSmallClockFrame.getHeight()).thenReturn(50);
@@ -218,8 +194,6 @@
 
     @Test
     public void testGetSmallLargeClockBottom_returnsExpectedValue() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         when(mLargeClockFrame.getVisibility()).thenReturn(View.GONE);
         when(mLargeClockFrame.getHeight()).thenReturn(100);
         when(mSmallClockFrame.getHeight()).thenReturn(50);
@@ -232,16 +206,12 @@
 
     @Test
     public void testGetClockBottom_nullClock_returnsZero() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         when(mClockEventController.getClock()).thenReturn(null);
         assertEquals(0, mController.getClockBottom(10));
     }
 
     @Test
     public void testChangeLockscreenWeatherEnabledSetsWeatherViewVisible() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         when(mSmartspaceController.isWeatherEnabled()).thenReturn(true);
         ArgumentCaptor<ContentObserver> observerCaptor =
                 ArgumentCaptor.forClass(ContentObserver.class);
@@ -260,8 +230,6 @@
 
     @Test
     public void testChangeClockDateWeatherEnabled_SetsDateWeatherViewVisibility() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
         when(mSmartspaceController.isEnabled()).thenReturn(true);
@@ -284,15 +252,11 @@
 
     @Test
     public void testGetClock_nullClock_returnsNull() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         when(mClockEventController.getClock()).thenReturn(null);
         assertNull(mController.getClock());
     }
 
     private void verifyAttachment(VerificationMode times) {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         verify(mClockRegistry, times).registerClockChangeListener(
                 any(ClockRegistry.ClockChangeListener.class));
         verify(mClockEventController, times).registerListeners(mView);
@@ -300,8 +264,6 @@
 
     @Test
     public void testSplitShadeEnabledSetToSmartspaceController() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         mController.setSplitShadeEnabled(true);
         verify(mSmartspaceController, times(1)).setSplitShadeEnabled(true);
         verify(mSmartspaceController, times(0)).setSplitShadeEnabled(false);
@@ -309,8 +271,6 @@
 
     @Test
     public void testSplitShadeDisabledSetToSmartspaceController() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         mController.setSplitShadeEnabled(false);
         verify(mSmartspaceController, times(1)).setSplitShadeEnabled(false);
         verify(mSmartspaceController, times(0)).setSplitShadeEnabled(true);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 83443be..0bf9d12 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.platform.test.annotations.DisableFlags;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -60,6 +61,7 @@
 // the main thread before acquiring a wake lock. This class is constructed when
 // the keyguard_clock_switch layout is inflated.
 @RunWithLooper(setAsMainLooper = true)
+@DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
 public class KeyguardClockSwitchTest extends SysuiTestCase {
     @Mock
     ViewGroup mMockKeyguardSliceView;
@@ -81,8 +83,6 @@
 
     @Before
     public void setUp() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         MockitoAnnotations.initMocks(this);
         when(mMockKeyguardSliceView.getContext()).thenReturn(mContext);
         when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area))
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
index b09357f..c51aa04 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
@@ -151,6 +151,7 @@
 
         if (!SceneContainerFlag.isEnabled()) {
             mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
+            //TODO move this to use @DisableFlags annotation if needed
             mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 693a877..7cc9185 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.ui.view.layout.sections
 
 import android.graphics.Point
+import android.platform.test.annotations.DisableFlags
 import android.view.WindowManager
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
@@ -106,8 +107,8 @@
     }
 
     @Test
+    @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun addViewsConditionally_migrateFlagOff() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
         mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
         val constraintLayout = ConstraintLayout(context, null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
index 201ee88..1c99eff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.ui.view.layout.sections
 
+import android.platform.test.annotations.EnableFlags
 import android.view.View
 import android.widget.LinearLayout
 import androidx.constraintlayout.widget.ConstraintLayout
@@ -48,6 +49,7 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
+@EnableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
 class SmartspaceSectionTest : SysuiTestCase() {
     private lateinit var underTest: SmartspaceSection
     @Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
@@ -70,7 +72,6 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         underTest =
             SmartspaceSection(
                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index b80d1a4..8a6b68f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -18,7 +18,6 @@
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
-import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -404,7 +403,6 @@
         mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false);
 
         mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
-        mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
         mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
 
         mMainDispatcher = getMainDispatcher();
@@ -801,7 +799,6 @@
                 .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
         verify(mNotificationStackScrollLayoutController)
                 .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture());
-        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
         reset(mKeyguardStatusViewController);
 
         when(mNotificationPanelViewControllerLazy.get())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 90e8ea5f..905cc4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -92,6 +92,7 @@
      * When the Back gesture starts (progress 0%), the scrim will stay at 100% scale (1.0f).
      */
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testBackGesture_min_scrimAtMaxScale() {
         mNotificationPanelViewController.onBackProgressed(0.0f);
         verify(mScrimController).applyBackScaling(1.0f);
@@ -101,6 +102,7 @@
      * When the Back gesture is at max (progress 100%), the scrim will be scaled to its minimum.
      */
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testBackGesture_max_scrimAtMinScale() {
         mNotificationPanelViewController.onBackProgressed(1.0f);
         verify(mScrimController).applyBackScaling(
@@ -108,6 +110,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onNotificationHeightChangeWhileOnKeyguardWillComputeMaxKeyguardNotifications() {
         mStatusBarStateController.setState(KEYGUARD);
         ArgumentCaptor<OnHeightChangedListener> captor =
@@ -124,6 +127,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onNotificationHeightChangeWhileInShadeWillNotComputeMaxKeyguardNotifications() {
         mStatusBarStateController.setState(SHADE);
         ArgumentCaptor<OnHeightChangedListener> captor =
@@ -140,6 +144,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void computeMaxKeyguardNotifications_lockscreenToShade_returnsExistingMax() {
         when(mAmbientState.getFractionToShade()).thenReturn(0.5f);
         mNotificationPanelViewController.setMaxDisplayedNotifications(-1);
@@ -150,6 +155,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void computeMaxKeyguardNotifications_noTransition_updatesMax() {
         when(mAmbientState.getFractionToShade()).thenReturn(0f);
         mNotificationPanelViewController.setMaxDisplayedNotifications(-1);
@@ -196,6 +202,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void getVerticalSpaceForLockscreenShelf_useLockIconBottomPadding_returnsShelfHeight() {
         enableSplitShade(/* enabled= */ false);
         setBottomPadding(/* stackScrollLayoutBottom= */ 100,
@@ -213,6 +220,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void getVerticalSpaceForLockscreenShelf_useIndicationBottomPadding_returnsZero() {
         enableSplitShade(/* enabled= */ false);
         setBottomPadding(/* stackScrollLayoutBottom= */ 100,
@@ -230,6 +238,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void getVerticalSpaceForLockscreenShelf_useAmbientBottomPadding_returnsZero() {
         enableSplitShade(/* enabled= */ false);
         setBottomPadding(/* stackScrollLayoutBottom= */ 100,
@@ -247,6 +256,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void getVerticalSpaceForLockscreenShelf_useLockIconPadding_returnsLessThanShelfHeight() {
         enableSplitShade(/* enabled= */ false);
         setBottomPadding(/* stackScrollLayoutBottom= */ 100,
@@ -264,6 +274,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void getVerticalSpaceForLockscreenShelf_splitShade() {
         enableSplitShade(/* enabled= */ true);
         setBottomPadding(/* stackScrollLayoutBottom= */ 100,
@@ -281,6 +292,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testSetPanelScrimMinFractionWhenHeadsUpIsDragged() {
         mNotificationPanelViewController.setHeadsUpDraggingStartingHeight(
                 mNotificationPanelViewController.getMaxPanelHeight() / 2);
@@ -288,6 +300,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testSetDozing_notifiesNsslAndStateController() {
         mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */);
         verify(mNotificationStackScrollLayoutController).setDozing(eq(true), eq(false));
@@ -295,6 +308,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testOnDozeAmountChanged_positionClockAndNotificationsUsesUdfpsLocation() {
         // GIVEN UDFPS is enrolled and we're on the keyguard
         final Point udfpsLocationCenter = new Point(0, 100);
@@ -332,12 +346,14 @@
 
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testSetExpandedHeight() {
         mNotificationPanelViewController.setExpandedHeight(200);
         assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testOnTouchEvent_expansionCanBeBlocked() {
         onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
         onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 200f, 0));
@@ -350,6 +366,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void test_pulsing_onTouchEvent_noTracking() {
         // GIVEN device is pulsing
         mNotificationPanelViewController.setPulsing(true);
@@ -367,6 +384,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void alternateBouncerVisible_onTouchEvent_notHandled() {
         mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
         // GIVEN alternate bouncer is visible
@@ -385,6 +403,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void test_onTouchEvent_startTracking() {
         // GIVEN device is NOT pulsing
         mNotificationPanelViewController.setPulsing(false);
@@ -402,9 +421,8 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onInterceptTouchEvent_nsslMigrationOff_userActivity() {
-        mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         mTouchHandler.onInterceptTouchEvent(MotionEvent.obtain(0L /* downTime */,
                 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
                 0 /* metaState */));
@@ -413,9 +431,8 @@
     }
 
     @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onInterceptTouchEvent_nsslMigrationOn_userActivity_not_called() {
-        mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-
         mTouchHandler.onInterceptTouchEvent(MotionEvent.obtain(0L /* downTime */,
                 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
                 0 /* metaState */));
@@ -424,6 +441,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testOnTouchEvent_expansionResumesAfterBriefTouch() {
         mFalsingManager.setIsClassifierEnabled(true);
         mFalsingManager.setIsFalseTouch(false);
@@ -460,6 +478,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testA11y_initializeNode() {
         AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
         mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
@@ -473,6 +492,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testA11y_scrollForward() {
         mAccessibilityDelegate.performAccessibilityAction(
                 mView,
@@ -483,6 +503,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testA11y_scrollUp() {
         mAccessibilityDelegate.performAccessibilityAction(
                 mView,
@@ -493,6 +514,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testKeyguardStatusViewInSplitShade_changesConstraintsDependingOnNotifications() {
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
@@ -511,6 +533,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void keyguardStatusView_splitShade_dozing_alwaysDozingOn_isCentered() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
         when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
@@ -523,6 +546,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void keyguardStatusView_splitShade_dozing_alwaysDozingOff_isNotCentered() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
         when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
@@ -535,6 +559,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void keyguardStatusView_splitShade_notDozing_alwaysDozingOn_isNotCentered() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
         when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
@@ -547,6 +572,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void keyguardStatusView_splitShade_pulsing_isNotCentered() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
         when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
@@ -560,6 +586,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void keyguardStatusView_splitShade_notPulsing_isNotCentered() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
         when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
@@ -573,6 +600,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void keyguardStatusView_singleShade_isCentered() {
         enableSplitShade(/* enabled= */ false);
         // The conditions below would make the clock NOT be centered on split shade.
@@ -587,6 +615,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenNot() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
         when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
@@ -602,6 +631,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void keyguardStatusView_willPlayDelayedDoze_notifiesKeyguardMediaController() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
         when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
@@ -614,6 +644,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
         when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
@@ -629,6 +660,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onKeyguardStatusViewHeightChange_animatesNextTopPaddingChangeForNSSL() {
         ArgumentCaptor<View.OnLayoutChangeListener> captor =
                 ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
@@ -646,6 +678,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testCanCollapsePanelOnTouch_trueForKeyGuard() {
         mStatusBarStateController.setState(KEYGUARD);
 
@@ -653,6 +686,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testCanCollapsePanelOnTouch_trueWhenScrolledToBottom() {
         mStatusBarStateController.setState(SHADE);
         when(mNotificationStackScrollLayoutController.isScrolledToBottom()).thenReturn(true);
@@ -661,6 +695,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testCanCollapsePanelOnTouch_trueWhenInSettings() {
         mStatusBarStateController.setState(SHADE);
         when(mQsController.getExpanded()).thenReturn(true);
@@ -669,6 +704,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testCanCollapsePanelOnTouch_falseInDualPaneShade() {
         mStatusBarStateController.setState(SHADE);
         enableSplitShade(/* enabled= */ true);
@@ -695,6 +731,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testCancelSwipeWhileLocked_notifiesKeyguardState() {
         mStatusBarStateController.setState(KEYGUARD);
 
@@ -707,6 +744,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testSwipe_exactlyToTarget_notifiesNssl() {
         // No over-expansion
         mNotificationPanelViewController.setOverExpansion(0f);
@@ -722,6 +760,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testRotatingToSplitShadeWithQsExpanded_transitionsToShadeLocked() {
         mStatusBarStateController.setState(KEYGUARD);
         when(mQsController.getExpanded()).thenReturn(true);
@@ -732,6 +771,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testUnlockedSplitShadeTransitioningToKeyguard_closesQS() {
         enableSplitShade(true);
         mStatusBarStateController.setState(SHADE);
@@ -741,6 +781,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testLockedSplitShadeTransitioningToKeyguard_closesQS() {
         enableSplitShade(true);
         mStatusBarStateController.setState(SHADE_LOCKED);
@@ -750,6 +791,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testSwitchesToCorrectClockInSinglePaneShade() {
         mStatusBarStateController.setState(KEYGUARD);
 
@@ -765,6 +807,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testSwitchesToCorrectClockInSplitShade() {
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
@@ -785,6 +828,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testHasNotifications_switchesToLargeClockWhenEnteringSplitShade() {
         mStatusBarStateController.setState(KEYGUARD);
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
@@ -796,6 +840,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testNoNotifications_switchesToLargeClockWhenEnteringSplitShade() {
         mStatusBarStateController.setState(KEYGUARD);
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
@@ -807,6 +852,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testHasNotifications_switchesToSmallClockWhenExitingSplitShade() {
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
@@ -820,6 +866,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testNoNotifications_switchesToLargeClockWhenExitingSplitShade() {
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
@@ -833,6 +880,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void clockSize_mediaShowing_inSplitShade_onAod_isLarge() {
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
         mStatusBarStateController.setState(KEYGUARD);
@@ -848,6 +896,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void clockSize_mediaShowing_inSplitShade_screenOff_notAod_isSmall() {
         when(mDozeParameters.getAlwaysOn()).thenReturn(false);
         mStatusBarStateController.setState(KEYGUARD);
@@ -863,6 +912,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_showNSSL() {
         // GIVEN
         mStatusBarStateController.setState(KEYGUARD);
@@ -883,6 +933,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_hideNSSL() {
         // GIVEN
         mStatusBarStateController.setState(KEYGUARD);
@@ -904,6 +955,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testSwitchesToBigClockInSplitShadeOnAodAnimateDisabled() {
         when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false);
         mStatusBarStateController.setState(KEYGUARD);
@@ -919,6 +971,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void switchesToBigClockInSplitShadeOn_landFlagOn_ForceSmallClock() {
         when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false);
         mStatusBarStateController.setState(KEYGUARD);
@@ -938,6 +991,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void switchesToBigClockInSplitShadeOn_landFlagOff_DontForceSmallClock() {
         when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false);
         mStatusBarStateController.setState(KEYGUARD);
@@ -957,6 +1011,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testDisplaysSmallClockOnLockscreenInSplitShadeWhenMediaIsPlaying() {
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
@@ -978,6 +1033,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testFoldToAodAnimationCleansupInAnimationEnd() {
         ArgumentCaptor<Animator.AnimatorListener> animCaptor =
                 ArgumentCaptor.forClass(Animator.AnimatorListener.class);
@@ -997,6 +1053,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testExpandWithQsMethodIsUsingLockscreenTransitionController() {
         enableSplitShade(/* enabled= */ true);
         mStatusBarStateController.setState(KEYGUARD);
@@ -1008,6 +1065,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void setKeyguardStatusBarAlpha_setsAlphaOnKeyguardStatusBarController() {
         float statusBarAlpha = 0.5f;
 
@@ -1017,6 +1075,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() {
         enableSplitShade(/* enabled= */ true);
         mShadeExpansionStateManager.updateState(STATE_OPEN);
@@ -1030,6 +1089,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testQsNotToBeImmediatelyExpandedWhenGoingFromUnlockedToLocked() {
         enableSplitShade(/* enabled= */ true);
         mShadeExpansionStateManager.updateState(STATE_CLOSED);
@@ -1042,6 +1102,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testQsImmediateResetsWhenPanelOpensOrCloses() {
         mShadeExpansionStateManager.updateState(STATE_OPEN);
         mShadeExpansionStateManager.updateState(STATE_CLOSED);
@@ -1049,6 +1110,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testQsExpansionChangedToDefaultWhenRotatingFromOrToSplitShade() {
         when(mCommandQueue.panelsEnabled()).thenReturn(true);
 
@@ -1065,6 +1127,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testPanelClosedWhenClosingQsInSplitShade() {
         mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
                 /* expanded= */ true, /* tracking= */ false);
@@ -1078,6 +1141,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void getMaxPanelTransitionDistance_expanding_inSplitShade_returnsSplitShadeFullTransitionDistance() {
         enableSplitShade(true);
         mNotificationPanelViewController.expandToQs();
@@ -1088,6 +1152,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void isExpandingOrCollapsing_returnsTrue_whenQsLockscreenDragInProgress() {
         when(mQsController.getLockscreenShadeDragProgress()).thenReturn(0.5f);
         assertThat(mNotificationPanelViewController.isExpandingOrCollapsing()).isTrue();
@@ -1095,6 +1160,7 @@
 
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void getMaxPanelTransitionDistance_inSplitShade_withHeadsUp_returnsBiggerValue() {
         enableSplitShade(true);
         mNotificationPanelViewController.expandToQs();
@@ -1111,6 +1177,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void getMaxPanelTransitionDistance_expandingSplitShade_keyguard_returnsNonSplitShadeValue() {
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(true);
@@ -1122,6 +1189,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void getMaxPanelTransitionDistance_expanding_notSplitShade_returnsNonSplitShadeValue() {
         enableSplitShade(false);
         mNotificationPanelViewController.expandToQs();
@@ -1132,6 +1200,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onLayoutChange_fullWidth_updatesQSWithFullWithTrue() {
         setIsFullWidth(true);
 
@@ -1139,6 +1208,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onLayoutChange_notFullWidth_updatesQSWithFullWithFalse() {
         setIsFullWidth(false);
 
@@ -1146,6 +1216,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onLayoutChange_qsNotSet_doesNotCrash() {
         mQuickSettingsController.setQs(null);
 
@@ -1153,6 +1224,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() {
         StatusBarStateController.StateListener statusBarStateListener =
                 mNotificationPanelViewController.getStatusBarStateListener();
@@ -1167,8 +1239,8 @@
     }
 
     @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void nsslFlagEnabled_allowOnlyExternalTouches() {
-        mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
 
         // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
         mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
@@ -1179,6 +1251,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onSplitShadeChanged_duringShadeExpansion_resetsOverScrollState() {
         // There was a bug where there was left-over overscroll state after going from split shade
         // to single shade.
@@ -1200,6 +1273,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onSplitShadeChanged_alwaysResetsOverScrollState() {
         enableSplitShade(true);
         enableSplitShade(false);
@@ -1217,6 +1291,7 @@
      * to ensure scrollY can be correctly set to be 0
      */
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped() {
         // Given: Shade is expanded
         mNotificationPanelViewController.notifyExpandingFinished();
@@ -1237,6 +1312,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onShadeFlingEnd_mExpandImmediateShouldBeReset() {
         mNotificationPanelViewController.onFlingEnd(false);
 
@@ -1244,6 +1320,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void inUnlockedSplitShade_transitioningMaxTransitionDistance_makesShadeFullyExpanded() {
         mStatusBarStateController.setState(SHADE);
         enableSplitShade(true);
@@ -1253,6 +1330,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void shadeFullyExpanded_inShadeState() {
         mStatusBarStateController.setState(SHADE);
 
@@ -1265,6 +1343,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void shadeFullyExpanded_onKeyguard() {
         mStatusBarStateController.setState(KEYGUARD);
 
@@ -1274,12 +1353,14 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void shadeFullyExpanded_onShadeLocked() {
         mStatusBarStateController.setState(SHADE_LOCKED);
         assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isTrue();
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void shadeExpanded_whenHasHeight() {
         int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
         mNotificationPanelViewController.setExpandedHeight(transitionDistance);
@@ -1287,6 +1368,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void shadeExpanded_whenInstantExpanding() {
         mNotificationPanelViewController.expand(true);
         assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
@@ -1300,12 +1382,14 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void shadeExpanded_whenUnlockedOffscreenAnimationRunning() {
         when(mUnlockedScreenOffAnimationController.isAnimationPlaying()).thenReturn(true);
         assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void shadeExpanded_whenInputFocusTransferStarted() {
         when(mCommandQueue.panelsEnabled()).thenReturn(true);
 
@@ -1315,6 +1399,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void shadeNotExpanded_whenInputFocusTransferStartedButPanelsDisabled() {
         when(mCommandQueue.panelsEnabled()).thenReturn(false);
 
@@ -1324,6 +1409,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void cancelInputFocusTransfer_shadeCollapsed() {
         when(mCommandQueue.panelsEnabled()).thenReturn(true);
         mNotificationPanelViewController.startInputFocusTransfer();
@@ -1334,6 +1420,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void finishInputFocusTransfer_shadeFlingingOpen() {
         when(mCommandQueue.panelsEnabled()).thenReturn(true);
         mNotificationPanelViewController.startInputFocusTransfer();
@@ -1344,6 +1431,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void getFalsingThreshold_deviceNotInteractive_isQsThreshold() {
         PowerInteractor.Companion.setAsleepForTest(
                 mPowerInteractor, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON);
@@ -1353,6 +1441,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void getFalsingThreshold_lastWakeNotDueToTouch_isQsThreshold() {
         PowerInteractor.Companion.setAwakeForTest(
                 mPowerInteractor, PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -1362,6 +1451,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void getFalsingThreshold_lastWakeDueToTouch_greaterThanQsThreshold() {
         PowerInteractor.Companion.setAwakeForTest(mPowerInteractor, PowerManager.WAKE_REASON_TAP);
         when(mQsController.getFalsingThreshold()).thenReturn(14);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index e1d92e7..52af907 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -18,6 +18,7 @@
 
 package com.android.systemui.shade
 
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper
 import android.view.HapticFeedbackConstants
@@ -27,6 +28,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.CollectionUtils
 import com.android.keyguard.KeyguardClockSwitch.LARGE
+import com.android.systemui.Flags
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarState.KEYGUARD
@@ -58,6 +60,7 @@
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
+@DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
 class NotificationPanelViewControllerWithCoroutinesTest :
     NotificationPanelViewControllerBaseTest() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 74a2999..6f2302a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.shade
 
 import android.content.Context
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.platform.test.annotations.RequiresFlagsDisabled
 import android.platform.test.flag.junit.FlagsParameterization
 import android.testing.TestableLooper
@@ -31,6 +33,7 @@
 import com.android.keyguard.LegacyLockIconViewController
 import com.android.keyguard.dagger.KeyguardBouncerComponent
 import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
@@ -398,8 +401,8 @@
 
     @Test
     @DisableSceneContainer
+    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun handleDispatchTouchEvent_nsslMigrationOff_userActivity_not_called() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         underTest.setStatusBarViewController(phoneStatusBarViewController)
 
         interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
@@ -408,8 +411,8 @@
     }
 
     @Test
+    @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun handleDispatchTouchEvent_nsslMigrationOn_userActivity() {
-        enableMigrateClocksFlag()
         underTest.setStatusBarViewController(phoneStatusBarViewController)
 
         interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
@@ -440,6 +443,7 @@
     }
 
     @Test
+    @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun shouldInterceptTouchEvent_dozing_touchInLockIconArea_touchNotIntercepted() {
         // GIVEN dozing
         whenever(sysuiStatusBarStateController.isDozing).thenReturn(true)
@@ -452,13 +456,12 @@
         // AND the lock icon wants the touch
         whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(true)
 
-        enableMigrateClocksFlag()
-
         // THEN touch should NOT be intercepted by NotificationShade
         assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse()
     }
 
     @Test
+    @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun shouldInterceptTouchEvent_dozing_touchNotInLockIconArea_touchIntercepted() {
         // GIVEN dozing
         whenever(sysuiStatusBarStateController.isDozing).thenReturn(true)
@@ -471,13 +474,12 @@
         whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any()))
                 .thenReturn(false)
 
-        enableMigrateClocksFlag()
-
         // THEN touch should be intercepted by NotificationShade
         assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
     }
 
     @Test
+    @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun shouldInterceptTouchEvent_dozing_touchInStatusBar_touchIntercepted() {
         // GIVEN dozing
         whenever(sysuiStatusBarStateController.isDozing).thenReturn(true)
@@ -490,13 +492,12 @@
         whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any()))
                 .thenReturn(true)
 
-        enableMigrateClocksFlag()
-
         // THEN touch should be intercepted by NotificationShade
         assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
     }
 
     @Test
+    @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun shouldInterceptTouchEvent_dozingAndPulsing_touchIntercepted() {
         // GIVEN dozing
         whenever(sysuiStatusBarStateController.isDozing).thenReturn(true)
@@ -517,8 +518,6 @@
         whenever(shadeViewController.handleExternalInterceptTouch(DOWN_EVENT))
                 .thenReturn(true)
 
-        enableMigrateClocksFlag()
-
         // THEN touch should be intercepted by NotificationShade
         assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
     }
@@ -652,19 +651,13 @@
     }
 
     @Test
+    @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun cancelCurrentTouch_callsDragDownHelper() {
-        enableMigrateClocksFlag()
         underTest.cancelCurrentTouch()
 
         verify(dragDownHelper).stopDragging()
     }
 
-    private fun enableMigrateClocksFlag() {
-        if (!Flags.migrateClocksToBlueprint()) {
-            mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-        }
-    }
-
     companion object {
         private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
         private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index fec7424..ca29dd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.shade
 
 import android.os.SystemClock
+import android.platform.test.annotations.DisableFlags
 import android.testing.TestableLooper.RunWithLooper
 import android.view.MotionEvent
 import android.widget.FrameLayout
@@ -208,9 +209,9 @@
     }
 
     @Test
+    @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun testDragDownHelperCalledWhenDraggingDown() =
         testScope.runTest {
-            mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
             whenever(dragDownHelper.isDraggingDown).thenReturn(true)
             val now = SystemClock.elapsedRealtime()
             val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index c9a7c82..02764f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
@@ -16,9 +16,13 @@
 
 package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel
 
+import android.content.DialogInterface
 import android.view.View
 import androidx.test.filters.SmallTest
+import com.android.internal.jank.Cuj
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
@@ -37,6 +41,8 @@
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.statusbar.policy.CastDevice
@@ -45,7 +51,10 @@
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -60,6 +69,16 @@
 
     private val mockScreenCastDialog = mock<SystemUIDialog>()
     private val mockGenericCastDialog = mock<SystemUIDialog>()
+    private val chipBackgroundView = mock<ChipBackgroundContainer>()
+    private val chipView =
+        mock<View>().apply {
+            whenever(
+                    this.requireViewById<ChipBackgroundContainer>(
+                        R.id.ongoing_activity_chip_background
+                    )
+                )
+                .thenReturn(chipBackgroundView)
+        }
 
     private val underTest = kosmos.castToOtherDeviceChipViewModel
 
@@ -193,6 +212,63 @@
         }
 
     @Test
+    fun chip_projectionStoppedFromDialog_chipImmediatelyHidden() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+            // WHEN the stop action on the dialog is clicked
+            val dialogStopAction =
+                getStopActionFromDialog(latest, chipView, mockScreenCastDialog, kosmos)
+            dialogStopAction.onClick(mock<DialogInterface>(), 0)
+
+            // THEN the chip is immediately hidden...
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+            // ...even though the repo still says it's projecting
+            assertThat(mediaProjectionRepo.mediaProjectionState.value)
+                .isInstanceOf(MediaProjectionState.Projecting::class.java)
+
+            // AND we specify no animation
+            assertThat((latest as OngoingActivityChipModel.Hidden).shouldAnimate).isFalse()
+        }
+
+    @Test
+    fun chip_routeStoppedFromDialog_chipImmediatelyHidden() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaRouterRepo.castDevices.value =
+                listOf(
+                    CastDevice(
+                        state = CastDevice.CastState.Connected,
+                        id = "id",
+                        name = "name",
+                        description = "desc",
+                        origin = CastDevice.CastOrigin.MediaRouter,
+                    )
+                )
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+            // WHEN the stop action on the dialog is clicked
+            val dialogStopAction =
+                getStopActionFromDialog(latest, chipView, mockGenericCastDialog, kosmos)
+            dialogStopAction.onClick(mock<DialogInterface>(), 0)
+
+            // THEN the chip is immediately hidden...
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+            // ...even though the repo still says it's projecting
+            assertThat(mediaRouterRepo.castDevices.value).isNotEmpty()
+
+            // AND we specify no animation
+            assertThat((latest as OngoingActivityChipModel.Hidden).shouldAnimate).isFalse()
+        }
+
+    @Test
     fun chip_colorsAreRed() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -297,8 +373,14 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
-            verify(mockScreenCastDialog).show()
+            clickListener!!.onClick(chipView)
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockScreenCastDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
         }
 
     @Test
@@ -316,8 +398,14 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
-            verify(mockScreenCastDialog).show()
+            clickListener!!.onClick(chipView)
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockScreenCastDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
         }
 
     @Test
@@ -339,7 +427,70 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
-            verify(mockGenericCastDialog).show()
+            clickListener!!.onClick(chipView)
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockGenericCastDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
+        }
+
+    @Test
+    fun chip_projectionStateCasting_clickListenerHasCuj() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
+
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            clickListener!!.onClick(chipView)
+
+            val cujCaptor = argumentCaptor<DialogCuj>()
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    any(),
+                    any(),
+                    cujCaptor.capture(),
+                    anyBoolean(),
+                )
+
+            assertThat(cujCaptor.firstValue.cujType)
+                .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
+            assertThat(cujCaptor.firstValue.tag).contains("Cast")
+        }
+
+    @Test
+    fun chip_routerStateCasting_clickListenerHasCuj() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaRouterRepo.castDevices.value =
+                listOf(
+                    CastDevice(
+                        state = CastDevice.CastState.Connected,
+                        id = "id",
+                        name = "name",
+                        description = "desc",
+                        origin = CastDevice.CastOrigin.MediaRouter,
+                    )
+                )
+
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            clickListener!!.onClick(chipView)
+
+            val cujCaptor = argumentCaptor<DialogCuj>()
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    any(),
+                    any(),
+                    cujCaptor.capture(),
+                    anyBoolean(),
+                )
+
+            assertThat(cujCaptor.firstValue.cujType)
+                .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
+            assertThat(cujCaptor.firstValue.tag).contains("Cast")
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index 4728c64..b4a37ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -16,9 +16,13 @@
 
 package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel
 
+import android.content.DialogInterface
 import android.view.View
 import androidx.test.filters.SmallTest
+import com.android.internal.jank.Cuj
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -30,9 +34,13 @@
 import com.android.systemui.res.R
 import com.android.systemui.screenrecord.data.model.ScreenRecordModel
 import com.android.systemui.screenrecord.data.repository.screenRecordRepository
+import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
 import com.android.systemui.statusbar.chips.screenrecord.ui.view.EndScreenRecordingDialogDelegate
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.util.time.fakeSystemClock
@@ -40,7 +48,10 @@
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -53,11 +64,22 @@
     private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository
     private val systemClock = kosmos.fakeSystemClock
     private val mockSystemUIDialog = mock<SystemUIDialog>()
+    private val chipBackgroundView = mock<ChipBackgroundContainer>()
+    private val chipView =
+        mock<View>().apply {
+            whenever(
+                    this.requireViewById<ChipBackgroundContainer>(
+                        R.id.ongoing_activity_chip_background
+                    )
+                )
+                .thenReturn(chipBackgroundView)
+        }
 
     private val underTest = kosmos.screenRecordChipViewModel
 
     @Before
     fun setUp() {
+        setUpPackageManagerForMediaProjection(kosmos)
         whenever(kosmos.mockSystemUIDialogFactory.create(any<EndScreenRecordingDialogDelegate>()))
             .thenReturn(mockSystemUIDialog)
     }
@@ -132,6 +154,40 @@
         }
 
     @Test
+    fun chip_recordingStoppedFromDialog_screenRecordAndShareToAppChipImmediatelyHidden() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            val latestShareToApp by collectLastValue(kosmos.shareToAppChipViewModel.chip)
+
+            // On real devices, when screen recording is active then share-to-app is also active
+            // because screen record is just a special case of share-to-app where the app receiving
+            // the share is SysUI
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen("fake.package")
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+            assertThat(latestShareToApp).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+            // WHEN the stop action on the dialog is clicked
+            val dialogStopAction =
+                getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+            dialogStopAction.onClick(mock<DialogInterface>(), 0)
+
+            // THEN both the screen record chip and the share-to-app chip are immediately hidden...
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+            assertThat(latestShareToApp).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+            // ...even though the repos still say it's recording
+            assertThat(screenRecordRepo.screenRecordState.value)
+                .isEqualTo(ScreenRecordModel.Recording)
+            assertThat(mediaProjectionRepo.mediaProjectionState.value)
+                .isInstanceOf(MediaProjectionState.Projecting::class.java)
+
+            // AND we specify no animation
+            assertThat((latest as OngoingActivityChipModel.Hidden).shouldAnimate).isFalse()
+        }
+
+    @Test
     fun chip_startingState_colorsAreRed() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -182,9 +238,15 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
+            clickListener!!.onClick(chipView)
             // EndScreenRecordingDialogDelegate will test that the dialog has the right message
-            verify(mockSystemUIDialog).show()
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockSystemUIDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
         }
 
     @Test
@@ -198,9 +260,15 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
+            clickListener!!.onClick(chipView)
             // EndScreenRecordingDialogDelegate will test that the dialog has the right message
-            verify(mockSystemUIDialog).show()
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockSystemUIDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
         }
 
     @Test
@@ -218,8 +286,39 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
+            clickListener!!.onClick(chipView)
             // EndScreenRecordingDialogDelegate will test that the dialog has the right message
-            verify(mockSystemUIDialog).show()
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockSystemUIDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
+        }
+
+    @Test
+    fun chip_clickListenerHasCuj() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen("host.package")
+
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            clickListener!!.onClick(chipView)
+
+            val cujCaptor = argumentCaptor<DialogCuj>()
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    any(),
+                    any(),
+                    cujCaptor.capture(),
+                    anyBoolean(),
+                )
+
+            assertThat(cujCaptor.firstValue.cujType)
+                .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
+            assertThat(cujCaptor.firstValue.tag).contains("Screen record")
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index f87b17d..2658679 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -16,9 +16,13 @@
 
 package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel
 
+import android.content.DialogInterface
 import android.view.View
 import androidx.test.filters.SmallTest
+import com.android.internal.jank.Cuj
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -34,6 +38,8 @@
 import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareToAppDialogDelegate
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.util.time.fakeSystemClock
@@ -41,7 +47,10 @@
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -54,6 +63,16 @@
     private val systemClock = kosmos.fakeSystemClock
 
     private val mockShareDialog = mock<SystemUIDialog>()
+    private val chipBackgroundView = mock<ChipBackgroundContainer>()
+    private val chipView =
+        mock<View>().apply {
+            whenever(
+                    this.requireViewById<ChipBackgroundContainer>(
+                        R.id.ongoing_activity_chip_background
+                    )
+                )
+                .thenReturn(chipBackgroundView)
+        }
 
     private val underTest = kosmos.shareToAppChipViewModel
 
@@ -134,6 +153,31 @@
         }
 
     @Test
+    fun chip_shareStoppedFromDialog_chipImmediatelyHidden() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+            // WHEN the stop action on the dialog is clicked
+            val dialogStopAction =
+                getStopActionFromDialog(latest, chipView, mockShareDialog, kosmos)
+            dialogStopAction.onClick(mock<DialogInterface>(), 0)
+
+            // THEN the chip is immediately hidden...
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+            // ...even though the repo still says it's projecting
+            assertThat(mediaProjectionRepo.mediaProjectionState.value)
+                .isInstanceOf(MediaProjectionState.Projecting::class.java)
+
+            // AND we specify no animation
+            assertThat((latest as OngoingActivityChipModel.Hidden).shouldAnimate).isFalse()
+        }
+
+    @Test
     fun chip_colorsAreRed() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -181,8 +225,14 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
-            verify(mockShareDialog).show()
+            clickListener!!.onClick(chipView)
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockShareDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
         }
 
     @Test
@@ -199,7 +249,41 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
-            verify(mockShareDialog).show()
+            clickListener!!.onClick(chipView)
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockShareDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
+        }
+
+    @Test
+    fun chip_clickListenerHasCuj() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.SingleTask(
+                    NORMAL_PACKAGE,
+                    hostDeviceName = null,
+                    createTask(taskId = 1),
+                )
+
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            clickListener!!.onClick(chipView)
+
+            val cujCaptor = argumentCaptor<DialogCuj>()
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    any(),
+                    any(),
+                    cujCaptor.capture(),
+                    anyBoolean(),
+                )
+
+            assertThat(cujCaptor.firstValue.cujType)
+                .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
+            assertThat(cujCaptor.firstValue.tag).contains("Share")
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
new file mode 100644
index 0000000..b9049e8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.ui.model.ColorsModel
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class ChipTransitionHelperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+
+    @Test
+    fun createChipFlow_typicallyFollowsInputFlow() =
+        testScope.runTest {
+            val underTest = ChipTransitionHelper(kosmos.applicationCoroutineScope)
+            val inputChipFlow =
+                MutableStateFlow<OngoingActivityChipModel>(OngoingActivityChipModel.Hidden())
+            val latest by collectLastValue(underTest.createChipFlow(inputChipFlow))
+
+            val newChip =
+                OngoingActivityChipModel.Shown.Timer(
+                    icon = Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+                    colors = ColorsModel.Themed,
+                    startTimeMs = 100L,
+                    onClickListener = null,
+                )
+
+            inputChipFlow.value = newChip
+
+            assertThat(latest).isEqualTo(newChip)
+
+            val newerChip =
+                OngoingActivityChipModel.Shown.IconOnly(
+                    icon = Icon.Resource(R.drawable.ic_hotspot, contentDescription = null),
+                    colors = ColorsModel.Themed,
+                    onClickListener = null,
+                )
+
+            inputChipFlow.value = newerChip
+
+            assertThat(latest).isEqualTo(newerChip)
+        }
+
+    @Test
+    fun activityStopped_chipHiddenWithoutAnimationFor500ms() =
+        testScope.runTest {
+            val underTest = ChipTransitionHelper(kosmos.applicationCoroutineScope)
+            val inputChipFlow =
+                MutableStateFlow<OngoingActivityChipModel>(OngoingActivityChipModel.Hidden())
+            val latest by collectLastValue(underTest.createChipFlow(inputChipFlow))
+
+            val shownChip =
+                OngoingActivityChipModel.Shown.Timer(
+                    icon = Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+                    colors = ColorsModel.Themed,
+                    startTimeMs = 100L,
+                    onClickListener = null,
+                )
+
+            inputChipFlow.value = shownChip
+
+            assertThat(latest).isEqualTo(shownChip)
+
+            // WHEN #onActivityStopped is invoked
+            underTest.onActivityStoppedFromDialog()
+            runCurrent()
+
+            // THEN the chip is hidden and has no animation
+            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
+
+            // WHEN only 250ms have elapsed
+            advanceTimeBy(250)
+
+            // THEN the chip is still hidden
+            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
+
+            // WHEN over 500ms have elapsed
+            advanceTimeBy(251)
+
+            // THEN the chip returns to the original input flow value
+            assertThat(latest).isEqualTo(shownChip)
+        }
+
+    @Test
+    fun activityStopped_stoppedAgainBefore500ms_chipReshownAfterSecond500ms() =
+        testScope.runTest {
+            val underTest = ChipTransitionHelper(kosmos.applicationCoroutineScope)
+            val inputChipFlow =
+                MutableStateFlow<OngoingActivityChipModel>(OngoingActivityChipModel.Hidden())
+            val latest by collectLastValue(underTest.createChipFlow(inputChipFlow))
+
+            val shownChip =
+                OngoingActivityChipModel.Shown.Timer(
+                    icon = Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+                    colors = ColorsModel.Themed,
+                    startTimeMs = 100L,
+                    onClickListener = null,
+                )
+
+            inputChipFlow.value = shownChip
+
+            assertThat(latest).isEqualTo(shownChip)
+
+            // WHEN #onActivityStopped is invoked
+            underTest.onActivityStoppedFromDialog()
+            runCurrent()
+
+            // THEN the chip is hidden and has no animation
+            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
+
+            // WHEN 250ms have elapsed, get another stop event
+            advanceTimeBy(250)
+            underTest.onActivityStoppedFromDialog()
+            runCurrent()
+
+            // THEN the chip is still hidden for another 500ms afterwards
+            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
+            advanceTimeBy(499)
+            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
+            advanceTimeBy(2)
+            assertThat(latest).isEqualTo(shownChip)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
index ca043f1..6e4d886 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
@@ -18,32 +18,58 @@
 
 import android.view.View
 import androidx.test.filters.SmallTest
+import com.android.internal.jank.Cuj
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import kotlin.test.Test
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @SmallTest
 class OngoingActivityChipViewModelTest : SysuiTestCase() {
     private val mockSystemUIDialog = mock<SystemUIDialog>()
     private val dialogDelegate = SystemUIDialog.Delegate { mockSystemUIDialog }
+    private val dialogTransitionAnimator = mock<DialogTransitionAnimator>()
+
+    private val chipBackgroundView = mock<ChipBackgroundContainer>()
+    private val chipView =
+        mock<View>().apply {
+            whenever(
+                    this.requireViewById<ChipBackgroundContainer>(
+                        R.id.ongoing_activity_chip_background
+                    )
+                )
+                .thenReturn(chipBackgroundView)
+        }
 
     @Test
     fun createDialogLaunchOnClickListener_showsDialogOnClick() {
+        val cuj = DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Test")
         val clickListener =
             createDialogLaunchOnClickListener(
                 dialogDelegate,
+                dialogTransitionAnimator,
+                cuj,
                 logcatLogBuffer("OngoingActivityChipViewModelTest"),
                 "tag",
             )
 
-        // Dialogs must be created on the main thread
-        context.mainExecutor.execute {
-            clickListener.onClick(mock<View>())
-            verify(mockSystemUIDialog).show()
-        }
+        clickListener.onClick(chipView)
+        verify(dialogTransitionAnimator)
+            .showFromView(
+                eq(mockSystemUIDialog),
+                eq(chipBackgroundView),
+                eq(cuj),
+                anyBoolean(),
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index b1a8d0b..ee249f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar.chips.ui.viewmodel
 
+import android.content.DialogInterface
+import android.content.packageManager
+import android.content.pm.PackageManager
+import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -33,10 +37,14 @@
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.runCurrent
@@ -44,9 +52,17 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
 class OngoingActivityChipsViewModelTest : SysuiTestCase() {
     private val kosmos = Kosmos().also { it.testCase = this }
     private val testScope = kosmos.testScope
@@ -56,6 +72,18 @@
     private val mediaProjectionState = kosmos.fakeMediaProjectionRepository.mediaProjectionState
     private val callRepo = kosmos.ongoingCallRepository
 
+    private val mockSystemUIDialog = mock<SystemUIDialog>()
+    private val chipBackgroundView = mock<ChipBackgroundContainer>()
+    private val chipView =
+        mock<View>().apply {
+            whenever(
+                    this.requireViewById<ChipBackgroundContainer>(
+                        R.id.ongoing_activity_chip_background
+                    )
+                )
+                .thenReturn(chipBackgroundView)
+        }
+
     private val underTest = kosmos.ongoingActivityChipsViewModel
 
     @Before
@@ -72,7 +100,7 @@
 
             val latest by collectLastValue(underTest.chip)
 
-            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden)
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
         }
 
     @Test
@@ -230,7 +258,81 @@
             job2.cancel()
         }
 
+    @Test
+    fun chip_screenRecordStoppedViaDialog_chipHiddenWithoutAnimation() =
+        testScope.runTest {
+            screenRecordState.value = ScreenRecordModel.Recording
+            mediaProjectionState.value = MediaProjectionState.NotProjecting
+            callRepo.setOngoingCallState(OngoingCallModel.NoCall)
+
+            val latest by collectLastValue(underTest.chip)
+
+            assertIsScreenRecordChip(latest)
+
+            // WHEN screen record gets stopped via dialog
+            val dialogStopAction =
+                getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+            dialogStopAction.onClick(mock<DialogInterface>(), 0)
+
+            // THEN the chip is immediately hidden with no animation
+            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
+        }
+
+    @Test
+    fun chip_projectionStoppedViaDialog_chipHiddenWithoutAnimation() =
+        testScope.runTest {
+            mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+            screenRecordState.value = ScreenRecordModel.DoingNothing
+            callRepo.setOngoingCallState(OngoingCallModel.NoCall)
+
+            val latest by collectLastValue(underTest.chip)
+
+            assertIsShareToAppChip(latest)
+
+            // WHEN media projection gets stopped via dialog
+            val dialogStopAction =
+                getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+            dialogStopAction.onClick(mock<DialogInterface>(), 0)
+
+            // THEN the chip is immediately hidden with no animation
+            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
+        }
+
     companion object {
+        /**
+         * Assuming that the click listener in [latest] opens a dialog, this fetches the action
+         * associated with the positive button, which we assume is the "Stop sharing" action.
+         */
+        fun getStopActionFromDialog(
+            latest: OngoingActivityChipModel?,
+            chipView: View,
+            dialog: SystemUIDialog,
+            kosmos: Kosmos
+        ): DialogInterface.OnClickListener {
+            // Capture the action that would get invoked when the user clicks "Stop" on the dialog
+            lateinit var dialogStopAction: DialogInterface.OnClickListener
+            Mockito.doAnswer {
+                    val delegate = it.arguments[0] as SystemUIDialog.Delegate
+                    delegate.beforeCreate(dialog, /* savedInstanceState= */ null)
+
+                    val stopActionCaptor = argumentCaptor<DialogInterface.OnClickListener>()
+                    verify(dialog).setPositiveButton(any(), stopActionCaptor.capture())
+                    dialogStopAction = stopActionCaptor.firstValue
+
+                    return@doAnswer dialog
+                }
+                .whenever(kosmos.mockSystemUIDialogFactory)
+                .create(any<SystemUIDialog.Delegate>())
+            whenever(kosmos.packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>()))
+                .thenThrow(PackageManager.NameNotFoundException())
+            // Click the chip so that we open the dialog and we fill in [dialogStopAction]
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            clickListener!!.onClick(chipView)
+
+            return dialogStopAction
+        }
+
         fun assertIsScreenRecordChip(latest: OngoingActivityChipModel?) {
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
             val icon = (latest as OngoingActivityChipModel.Shown).icon
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
index fea0e72..8dfbb37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
@@ -118,8 +118,8 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void testAppearResetsTranslation() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
         mController.setupAodIcons(mAodIcons);
         when(mDozeParameters.shouldControlScreenOff()).thenReturn(false);
         mController.appearAodIcons();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 01540e7..58ad835 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -536,7 +536,7 @@
 
         // WHEN there's *no* ongoing activity via new callback
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ false);
+                /* hasOngoingActivity= */ false, /* shouldAnimate= */ false);
 
         // THEN the old callback value is used, so the view is shown
         assertEquals(View.VISIBLE,
@@ -548,7 +548,7 @@
 
         // WHEN there *is* an ongoing activity via new callback
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ true);
+                /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
         // THEN the old callback value is used, so the view is hidden
         assertEquals(View.GONE,
@@ -565,7 +565,7 @@
         // listener, but I'm unable to get the fragment to get attached so that the binder starts
         // listening to flows.
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ false);
+                /* hasOngoingActivity= */ false, /* shouldAnimate= */ false);
 
         assertEquals(View.GONE,
                 mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
@@ -577,7 +577,7 @@
         resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ true);
+                /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
         assertEquals(View.VISIBLE,
                 mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
@@ -590,7 +590,7 @@
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ true);
+                /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
         fragment.disable(DEFAULT_DISPLAY,
                 StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
@@ -605,7 +605,7 @@
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ true);
+                /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
         when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
@@ -621,14 +621,14 @@
 
         // Ongoing activity started
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ true);
+                /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
         assertEquals(View.VISIBLE,
                 mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
 
         // Ongoing activity ended
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ false);
+                /* hasOngoingActivity= */ false, /* shouldAnimate= */ false);
 
         assertEquals(View.GONE,
                 mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
@@ -643,7 +643,7 @@
 
         // Ongoing call started
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ true);
+                /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
         // Notification area is hidden without delay
         assertEquals(0f, getNotificationAreaView().getAlpha(), 0.01);
@@ -661,7 +661,7 @@
 
         // WHEN there's *no* ongoing activity via new callback
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ false);
+                /* hasOngoingActivity= */ false, /* shouldAnimate= */ false);
 
         // THEN the new callback value is used, so the view is hidden
         assertEquals(View.GONE,
@@ -673,7 +673,7 @@
 
         // WHEN there *is* an ongoing activity via new callback
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ true);
+                /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
         // THEN the new callback value is used, so the view is shown
         assertEquals(View.VISIBLE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index 94159bc..60750cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -425,7 +425,7 @@
 
             kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing
 
-            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden)
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
 
             kosmos.fakeMediaProjectionRepository.mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
index d3f1125..cefdf7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
@@ -29,7 +29,7 @@
     override val transitionFromLockscreenToDreamStartedEvent = MutableSharedFlow<Unit>()
 
     override val ongoingActivityChip: MutableStateFlow<OngoingActivityChipModel> =
-        MutableStateFlow(OngoingActivityChipModel.Hidden)
+        MutableStateFlow(OngoingActivityChipModel.Hidden())
 
     override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index ac42319..60b5b5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -86,6 +86,7 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.ZenModeConfig;
 import android.testing.TestableLooper;
+import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
 import android.view.Display;
@@ -183,6 +184,7 @@
 import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
@@ -192,7 +194,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -216,6 +217,9 @@
 @RunWith(ParameterizedAndroidJunit4.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class BubblesTest extends SysuiTestCase {
+
+    private static final String TAG = "BubblesTest";
+
     @Mock
     private CommonNotifCollection mCommonNotifCollection;
     @Mock
@@ -241,8 +245,6 @@
     @Mock
     private KeyguardBypassController mKeyguardBypassController;
     @Mock
-    private FloatingContentCoordinator mFloatingContentCoordinator;
-    @Mock
     private BubbleDataRepository mDataRepository;
     @Mock
     private NotificationShadeWindowView mNotificationShadeWindowView;
@@ -372,6 +374,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        PhysicsAnimatorTestUtils.prepareForTest();
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             doReturn(true).when(mTransitions).isRegistered();
@@ -494,7 +497,7 @@
                 mShellCommandHandler,
                 mShellController,
                 mBubbleData,
-                mFloatingContentCoordinator,
+                new FloatingContentCoordinator(),
                 mDataRepository,
                 mStatusBarService,
                 mWindowManager,
@@ -571,12 +574,32 @@
     }
 
     @After
-    public void tearDown() {
+    public void tearDown() throws Exception {
         ArrayList<Bubble> bubbles = new ArrayList<>(mBubbleData.getBubbles());
         for (int i = 0; i < bubbles.size(); i++) {
             mBubbleController.removeBubble(bubbles.get(i).getKey(),
                     Bubbles.DISMISS_NO_LONGER_BUBBLE);
         }
+        mTestableLooper.processAllMessages();
+
+        // check that no animations are running before finishing the test to make sure that the
+        // state gets cleaned up correctly between tests.
+        int retryCount = 0;
+        while (PhysicsAnimatorTestUtils.isAnyAnimationRunning() && retryCount <= 10) {
+            Log.d(
+                    TAG,
+                    String.format("waiting for animations to complete. attempt %d", retryCount));
+            // post a message to the looper and wait for it to be processed
+            mTestableLooper.runWithLooper(() -> {});
+            retryCount++;
+        }
+        mTestableLooper.processAllMessages();
+        if (PhysicsAnimatorTestUtils.isAnyAnimationRunning()) {
+            Log.d(TAG, "finished waiting for animations to complete but animations are still "
+                    + "running");
+        } else {
+            Log.d(TAG, "no animations are running");
+        }
     }
 
     @Test
@@ -1853,7 +1876,6 @@
                 any(Bubble.class), anyBoolean(), anyBoolean());
     }
 
-    @Ignore("reason = b/351977103")
     @Test
     public void testShowStackEdu_isNotConversationBubble() {
         // Setup
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
index 3d85a4a..c7dfd5c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel
 
 import android.content.applicationContext
+import com.android.systemui.animation.dialogTransitionAnimator
+import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.mediaRouterChipInteractor
@@ -34,6 +36,7 @@
             mediaRouterChipInteractor = mediaRouterChipInteractor,
             systemClock = fakeSystemClock,
             endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
+            dialogTransitionAnimator = mockDialogTransitionAnimator,
             logger = statusBarChipsLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
index 1ed7a47..651a0f7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.chips.mediaprojection.ui.view
 
 import android.content.packageManager
+import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 
@@ -24,6 +25,7 @@
     Kosmos.Fixture {
         EndMediaProjectionDialogHelper(
             dialogFactory = mockSystemUIDialogFactory,
+            dialogTransitionAnimator = mockDialogTransitionAnimator,
             packageManager = packageManager,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
index e4bb166..c2a6f7d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
@@ -17,10 +17,12 @@
 package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel
 
 import android.content.applicationContext
+import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
 import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.screenRecordChipInteractor
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
 import com.android.systemui.statusbar.chips.statusBarChipsLogger
 import com.android.systemui.util.time.fakeSystemClock
 
@@ -30,7 +32,9 @@
             scope = applicationCoroutineScope,
             context = applicationContext,
             interactor = screenRecordChipInteractor,
+            shareToAppChipViewModel = shareToAppChipViewModel,
             endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
+            dialogTransitionAnimator = mockDialogTransitionAnimator,
             systemClock = fakeSystemClock,
             logger = statusBarChipsLogger,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
index 8ed7f96..0770009 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel
 
 import android.content.applicationContext
+import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
@@ -32,6 +33,7 @@
             mediaProjectionChipInteractor = mediaProjectionChipInteractor,
             systemClock = fakeSystemClock,
             endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
+            dialogTransitionAnimator = mockDialogTransitionAnimator,
             logger = statusBarChipsLogger,
         )
     }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index f61ca61..c82e5be 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -16,6 +16,8 @@
 
 package com.android.server.inputmethod;
 
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
+
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.NonNull;
@@ -110,7 +112,7 @@
             InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback cb);
 
     /**
-     * Force switch to the enabled input method by {@code imeId} for current user. If the input
+     * Force switch to the enabled input method by {@code imeId} for the current user. If the input
      * method with {@code imeId} is not enabled or not installed, do nothing.
      *
      * @param imeId  the input method ID to be switched to
@@ -119,7 +121,25 @@
      * method by {@code imeId}; {@code false} the input method with {@code imeId} is not available
      * to be switched.
      */
-    public abstract boolean switchToInputMethod(String imeId, @UserIdInt int userId);
+    public boolean switchToInputMethod(@NonNull String imeId, @UserIdInt int userId) {
+        return switchToInputMethod(imeId, NOT_A_SUBTYPE_ID, userId);
+    }
+
+    /**
+     * Force switch to the enabled input method by {@code imeId} for the current user. If the input
+     * method with {@code imeId} is not enabled or not installed, do nothing. If {@code subtypeId}
+     * is also supplied (not {@link InputMethodUtils#NOT_A_SUBTYPE_ID}) and valid, also switches to
+     * it, otherwise the system decides the most sensible default subtype to use.
+     *
+     * @param imeId the input method ID to be switched to
+     * @param subtypeId the input method subtype ID to be switched to
+     * @param userId the user ID to be queried
+     * @return {@code true} if the current input method was successfully switched to the input
+     * method by {@code imeId}; {@code false} the input method with {@code imeId} is not available
+     * to be switched.
+     */
+    public abstract boolean switchToInputMethod(@NonNull String imeId, int subtypeId,
+            @UserIdInt int userId);
 
     /**
      * Force enable or disable the input method associated with {@code imeId} for given user. If
@@ -211,6 +231,15 @@
     public abstract void updateImeWindowStatus(boolean disableImeIcon, int displayId);
 
     /**
+     * Updates and reports whether the IME switcher button should be shown, regardless whether
+     * SystemUI or the IME is responsible for drawing it and the corresponding navigation bar.
+     *
+     * @param displayId the display for which to update the IME switcher button visibility.
+     * @param userId    the user for which to update the IME switcher button visibility.
+     */
+    public abstract void updateShouldShowImeSwitcher(int displayId, @UserIdInt int userId);
+
+    /**
      * Finish stylus handwriting by calling {@link InputMethodService#finishStylusHandwriting()} if
      * there is an ongoing handwriting session.
      */
@@ -290,7 +319,8 @@
                 }
 
                 @Override
-                public boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
+                public boolean switchToInputMethod(@NonNull String imeId, int subtypeId,
+                        @UserIdInt int userId) {
                     return false;
                 }
 
@@ -335,6 +365,10 @@
                 }
 
                 @Override
+                public void updateShouldShowImeSwitcher(int displayId, @UserIdInt int userId) {
+                }
+
+                @Override
                 public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
                         IAccessibilityInputMethodSession session, @UserIdInt int userId) {
                 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 85af7ab..fbd9ac0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -181,6 +181,7 @@
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener;
+import com.android.server.inputmethod.InputMethodMenuControllerNew.MenuItem;
 import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -360,6 +361,7 @@
     private final UserManagerInternal mUserManagerInternal;
     @MultiUserUnawareField
     private final InputMethodMenuController mMenuController;
+    private final InputMethodMenuControllerNew mMenuControllerNew;
 
     @GuardedBy("ImfLock.class")
     @MultiUserUnawareField
@@ -566,7 +568,9 @@
         }
         switch (key) {
             case Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD: {
-                mMenuController.updateKeyboardFromSettingsLocked();
+                if (!Flags.imeSwitcherRevamp()) {
+                    mMenuController.updateKeyboardFromSettingsLocked();
+                }
                 break;
             }
             case Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE: {
@@ -631,7 +635,15 @@
                         }
                     }
                 }
-                mMenuController.hideInputMethodMenu();
+                if (Flags.imeSwitcherRevamp()) {
+                    synchronized (ImfLock.class) {
+                        final var bindingController = getInputMethodBindingController(senderUserId);
+                        mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(),
+                                senderUserId);
+                    }
+                } else {
+                    mMenuController.hideInputMethodMenu();
+                }
             } else {
                 Slog.w(TAG, "Unexpected intent " + intent);
             }
@@ -1171,6 +1183,8 @@
                             : bindingControllerFactory);
 
             mMenuController = new InputMethodMenuController(this);
+            mMenuControllerNew = Flags.imeSwitcherRevamp()
+                    ? new InputMethodMenuControllerNew() : null;
             mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
             mVisibilityApplier = new DefaultImeVisibilityApplier(this);
 
@@ -1782,7 +1796,11 @@
                     ImeTracker.PHASE_SERVER_WAIT_IME);
             userData.mCurStatsToken = null;
             // TODO: Make mMenuController multi-user aware
-            mMenuController.hideInputMethodMenuLocked();
+            if (Flags.imeSwitcherRevamp()) {
+                mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
+            } else {
+                mMenuController.hideInputMethodMenuLocked();
+            }
         }
     }
 
@@ -2599,7 +2617,12 @@
         if (!mShowOngoingImeSwitcherForPhones) return false;
         // When the IME switcher dialog is shown, the IME switcher button should be hidden.
         // TODO(b/305849394): Make mMenuController multi-user aware.
-        if (mMenuController.getSwitchingDialogLocked() != null) return false;
+        final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
+                ? mMenuControllerNew.isShowing()
+                : mMenuController.getSwitchingDialogLocked() != null;
+        if (switcherMenuShowing) {
+            return false;
+        }
         // When we are switching IMEs, the IME switcher button should be hidden.
         final var bindingController = getInputMethodBindingController(userId);
         if (!Objects.equals(bindingController.getCurId(),
@@ -2614,7 +2637,7 @@
                 || (visibility & InputMethodService.IME_INVISIBLE) != 0) {
             return false;
         }
-        if (mWindowManagerInternal.isHardKeyboardAvailable()) {
+        if (mWindowManagerInternal.isHardKeyboardAvailable() && !Flags.imeSwitcherRevamp()) {
             // When physical keyboard is attached, we show the ime switcher (or notification if
             // NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently
             // exists in the IME switcher dialog.  Might be OK to remove this condition once
@@ -2625,6 +2648,15 @@
         }
 
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+        if (Flags.imeSwitcherRevamp()) {
+            // The IME switcher button should be shown when the current IME specified a
+            // language settings activity.
+            final var curImi = settings.getMethodMap().get(settings.getSelectedInputMethod());
+            if (curImi != null && curImi.createImeLanguageSettingsActivityIntent() != null) {
+                return true;
+            }
+        }
+
         return hasMultipleSubtypesForSwitcher(false /* nonAuxOnly */, settings);
     }
 
@@ -2794,7 +2826,10 @@
             }
             final var curId = bindingController.getCurId();
             // TODO(b/305849394): Make mMenuController multi-user aware.
-            if (mMenuController.getSwitchingDialogLocked() != null
+            final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
+                    ? mMenuControllerNew.isShowing()
+                    : mMenuController.getSwitchingDialogLocked() != null;
+            if (switcherMenuShowing
                     || !Objects.equals(curId, bindingController.getSelectedMethodId())) {
                 // When the IME switcher dialog is shown, or we are switching IMEs,
                 // the back button should be in the default state (as if the IME is not shown).
@@ -2813,7 +2848,9 @@
     @GuardedBy("ImfLock.class")
     void updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) {
         updateInputMethodsFromSettingsLocked(enabledMayChange, userId);
-        mMenuController.updateKeyboardFromSettingsLocked();
+        if (!Flags.imeSwitcherRevamp()) {
+            mMenuController.updateKeyboardFromSettingsLocked();
+        }
     }
 
     /**
@@ -3979,10 +4016,70 @@
     @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
     public boolean isInputMethodPickerShownForTest() {
         synchronized (ImfLock.class) {
-            return mMenuController.isisInputMethodPickerShownForTestLocked();
+            return Flags.imeSwitcherRevamp()
+                    ? mMenuControllerNew.isShowing()
+                    : mMenuController.isisInputMethodPickerShownForTestLocked();
         }
     }
 
+    /**
+     * Gets the list of Input Method Switcher Menu items and the index of the selected item.
+     *
+     * @param items             the list of input method and subtype items.
+     * @param selectedImeId     the ID of the selected input method.
+     * @param selectedSubtypeId the ID of the selected input method subtype,
+     *                          or {@link #NOT_A_SUBTYPE_ID} if no subtype is selected.
+     * @param userId            the ID of the user for which to get the menu items.
+     * @return the list of menu items, and the index of the selected item,
+     * or {@code -1} if no item is selected.
+     */
+    @GuardedBy("ImfLock.class")
+    @NonNull
+    private Pair<List<MenuItem>, Integer> getInputMethodPickerItems(
+            @NonNull List<ImeSubtypeListItem> items, @Nullable String selectedImeId,
+            int selectedSubtypeId, @UserIdInt int userId) {
+        final var bindingController = getInputMethodBindingController(userId);
+        final var settings = InputMethodSettingsRepository.get(userId);
+
+        if (selectedSubtypeId == NOT_A_SUBTYPE_ID) {
+            // TODO(b/351124299): Check if this fallback logic is still necessary.
+            final var curSubtype = bindingController.getCurrentInputMethodSubtype();
+            if (curSubtype != null) {
+                final var curMethodId = bindingController.getSelectedMethodId();
+                final var curImi = settings.getMethodMap().get(curMethodId);
+                selectedSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(
+                        curImi, curSubtype.hashCode());
+            }
+        }
+
+        // No item is selected by default. When we have a list of explicitly enabled
+        // subtypes, the implicit subtype is no longer listed. If the implicit one
+        // is still selected, no items will be shown as selected.
+        int selectedIndex = -1;
+        String prevImeId = null;
+        final var menuItems = new ArrayList<MenuItem>();
+        for (int i = 0; i < items.size(); i++) {
+            final var item = items.get(i);
+            final var imeId = item.mImi.getId();
+            if (imeId.equals(selectedImeId)) {
+                final int subtypeId = item.mSubtypeId;
+                // Check if this is the selected IME-subtype pair.
+                if ((subtypeId == 0 && selectedSubtypeId == NOT_A_SUBTYPE_ID)
+                        || subtypeId == NOT_A_SUBTYPE_ID
+                        || subtypeId == selectedSubtypeId) {
+                    selectedIndex = i;
+                }
+            }
+            final boolean hasHeader = !imeId.equals(prevImeId);
+            final boolean hasDivider = hasHeader && prevImeId != null;
+            prevImeId = imeId;
+            menuItems.add(new MenuItem(item.mImeName, item.mSubtypeName, item.mImi, item.mSubtypeId,
+                    hasHeader, hasDivider));
+        }
+
+        return new Pair<>(menuItems, selectedIndex);
+    }
+
     @BinderThread
     private void onImeSwitchButtonClickFromClient(@NonNull IBinder token, int displayId,
             @NonNull UserData userData) {
@@ -4625,7 +4722,10 @@
             proto.write(IS_INTERACTIVE, mIsInteractive);
             proto.write(BACK_DISPOSITION, bindingController.getBackDisposition());
             proto.write(IME_WINDOW_VISIBILITY, bindingController.getImeWindowVis());
-            proto.write(SHOW_IME_WITH_HARD_KEYBOARD, mMenuController.getShowImeWithHardKeyboard());
+            if (!Flags.imeSwitcherRevamp()) {
+                proto.write(SHOW_IME_WITH_HARD_KEYBOARD,
+                        mMenuController.getShowImeWithHardKeyboard());
+            }
             proto.write(CONCURRENT_MULTI_USER_MODE_ENABLED, mConcurrentMultiUserModeEnabled);
             proto.end(token);
         }
@@ -4931,8 +5031,9 @@
                 synchronized (ImfLock.class) {
                     final InputMethodSettings settings =
                             InputMethodSettingsRepository.get(mCurrentUserId);
+                    final int userId = settings.getUserId();
                     final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
-                            && mWindowManagerInternal.isKeyguardSecure(settings.getUserId());
+                            && mWindowManagerInternal.isKeyguardSecure(userId);
                     final String lastInputMethodId = settings.getSelectedInputMethod();
                     int lastInputMethodSubtypeId =
                             settings.getSelectedInputMethodSubtypeId(lastInputMethodId);
@@ -4945,12 +5046,35 @@
                         Slog.w(TAG, "Show switching menu failed, imList is empty,"
                                 + " showAuxSubtypes: " + showAuxSubtypes
                                 + " isScreenLocked: " + isScreenLocked
-                                + " userId: " + settings.getUserId());
+                                + " userId: " + userId);
                         return false;
                     }
 
-                    mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
-                            lastInputMethodId, lastInputMethodSubtypeId, imList);
+                    if (Flags.imeSwitcherRevamp()) {
+                        if (DEBUG) {
+                            Slog.v(TAG, "Show IME switcher menu,"
+                                    + " showAuxSubtypes=" + showAuxSubtypes
+                                    + " displayId=" + displayId
+                                    + " preferredInputMethodId=" + lastInputMethodId
+                                    + " preferredInputMethodSubtypeId=" + lastInputMethodSubtypeId);
+                        }
+
+                        final var itemsAndIndex = getInputMethodPickerItems(imList,
+                                lastInputMethodId, lastInputMethodSubtypeId, userId);
+                        final var menuItems = itemsAndIndex.first;
+                        final int selectedIndex = itemsAndIndex.second;
+
+                        if (selectedIndex == -1) {
+                            Slog.w(TAG, "Switching menu shown with no item selected"
+                                    + ", IME id: " + lastInputMethodId
+                                    + ", subtype index: " + lastInputMethodSubtypeId);
+                        }
+
+                        mMenuControllerNew.show(menuItems, selectedIndex, displayId, userId);
+                    } else {
+                        mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
+                                lastInputMethodId, lastInputMethodSubtypeId, imList);
+                    }
                 }
                 return true;
 
@@ -5021,7 +5145,9 @@
 
             // --------------------------------------------------------------
             case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
-                mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
+                if (!Flags.imeSwitcherRevamp()) {
+                    mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
+                }
                 synchronized (ImfLock.class) {
                     sendOnNavButtonFlagsChangedToAllImesLocked();
                 }
@@ -5591,7 +5717,8 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
+    private boolean switchToInputMethodLocked(@NonNull String imeId, int subtypeId,
+            @UserIdInt int userId) {
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         if (mConcurrentMultiUserModeEnabled || userId == mCurrentUserId) {
             if (!settings.getMethodMap().containsKey(imeId)
@@ -5599,7 +5726,7 @@
                     .contains(settings.getMethodMap().get(imeId))) {
                 return false; // IME is not found or not enabled.
             }
-            setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID, userId);
+            setInputMethodLocked(imeId, subtypeId, userId);
             return true;
         }
         if (!settings.getMethodMap().containsKey(imeId)
@@ -5608,6 +5735,7 @@
             return false; // IME is not found or not enabled.
         }
         settings.putSelectedInputMethod(imeId);
+        // For non-current user, only reset subtypeId (instead of setting the given one).
         settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
         return true;
     }
@@ -5753,9 +5881,10 @@
         }
 
         @Override
-        public boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
+        public boolean switchToInputMethod(@NonNull String imeId, int subtypeId,
+                @UserIdInt int userId) {
             synchronized (ImfLock.class) {
-                return switchToInputMethodLocked(imeId, userId);
+                return switchToInputMethodLocked(imeId, subtypeId, userId);
             }
         }
 
@@ -5852,7 +5981,12 @@
                 // input target changed, in case seeing the dialog dismiss flickering during
                 // the next focused window starting the input connection.
                 if (mLastImeTargetWindow != userData.mImeBindingState.mFocusedWindow) {
-                    mMenuController.hideInputMethodMenuLocked();
+                    if (Flags.imeSwitcherRevamp()) {
+                        final var bindingController = getInputMethodBindingController(userId);
+                        mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
+                    } else {
+                        mMenuController.hideInputMethodMenuLocked();
+                    }
                 }
             }
         }
@@ -5871,6 +6005,15 @@
         }
 
         @Override
+        public void updateShouldShowImeSwitcher(int displayId, @UserIdInt int userId) {
+            synchronized (ImfLock.class) {
+                updateSystemUiLocked(userId);
+                final var userData = getUserData(userId);
+                sendOnNavButtonFlagsChangedLocked(userData);
+            }
+        }
+
+        @Override
         public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
                 IAccessibilityInputMethodSession session, @UserIdInt int userId) {
             synchronized (ImfLock.class) {
@@ -6192,6 +6335,10 @@
                     };
             mUserDataRepository.forAllUserData(userDataDump);
 
+            if (Flags.imeSwitcherRevamp()) {
+                p.println("  menuControllerNew:");
+                mMenuControllerNew.dump(p, "  ");
+            }
             p.println("  mCurToken=" + bindingController.getCurToken());
             p.println("  mCurTokenDisplayId=" + bindingController.getCurTokenDisplayId());
             p.println("  mCurHostInputToken=" + bindingController.getCurHostInputToken());
@@ -6638,7 +6785,7 @@
                         continue;
                     }
                     boolean failedToSelectUnknownIme = !switchToInputMethodLocked(imeId,
-                            userId);
+                            NOT_A_SUBTYPE_ID, userId);
                     if (failedToSelectUnknownIme) {
                         error.print("Unknown input method ");
                         error.print(imeId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
new file mode 100644
index 0000000..cbb1807
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+
+import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.Printer;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodInfo;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.widget.RecyclerView;
+
+import java.util.List;
+
+/**
+ * Controller for showing and hiding the Input Method Switcher Menu.
+ */
+final class InputMethodMenuControllerNew {
+
+    private static final String TAG = InputMethodMenuControllerNew.class.getSimpleName();
+
+    /**
+     * The horizontal offset from the menu to the edge of the screen corresponding
+     * to {@link Gravity#END}.
+     */
+    private static final int HORIZONTAL_OFFSET = 16;
+
+    /** The title of the window, used for debugging. */
+    private static final String WINDOW_TITLE = "IME Switcher Menu";
+
+    private final InputMethodDialogWindowContext mDialogWindowContext =
+            new InputMethodDialogWindowContext();
+
+    @Nullable
+    private AlertDialog mDialog;
+
+    @Nullable
+    private List<MenuItem> mMenuItems;
+
+    /**
+     * Shows the Input Method Switcher Menu, with a list of IMEs and their subtypes.
+     *
+     * @param items         the list of menu items.
+     * @param selectedIndex the index of the menu item that is selected.
+     *                      If no other IMEs are enabled, this index will be out of reach.
+     * @param displayId     the ID of the display where the menu was requested.
+     * @param userId        the ID of the user that requested the menu.
+     */
+    void show(@NonNull List<MenuItem> items, int selectedIndex, int displayId,
+            @UserIdInt int userId) {
+        // Hide the menu in case it was already showing.
+        hide(displayId, userId);
+
+        final Context dialogWindowContext = mDialogWindowContext.get(displayId);
+        final var builder = new AlertDialog.Builder(dialogWindowContext,
+                com.android.internal.R.style.Theme_DeviceDefault_InputMethodSwitcherDialog);
+        final var inflater = LayoutInflater.from(builder.getContext());
+
+        // Create the content view.
+        final View contentView = inflater
+                .inflate(com.android.internal.R.layout.input_method_switch_dialog_new, null);
+        contentView.setAccessibilityPaneTitle(
+                dialogWindowContext.getText(com.android.internal.R.string.select_input_method));
+        builder.setView(contentView);
+
+        final DialogInterface.OnClickListener onClickListener = (dialog, which) -> {
+            if (which != selectedIndex) {
+                final var item = items.get(which);
+                InputMethodManagerInternal.get()
+                        .switchToInputMethod(item.mImi.getId(), item.mSubtypeId, userId);
+            }
+            hide(displayId, userId);
+        };
+
+        final var selectedImi = selectedIndex >= 0 ? items.get(selectedIndex).mImi : null;
+        final var languageSettingsIntent = selectedImi != null
+                ? selectedImi.createImeLanguageSettingsActivityIntent() : null;
+        final boolean hasLanguageSettingsButton = languageSettingsIntent != null;
+        if (hasLanguageSettingsButton) {
+            final View buttonBar = contentView
+                    .requireViewById(com.android.internal.R.id.button_bar);
+            buttonBar.setVisibility(View.VISIBLE);
+
+            languageSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            final Button languageSettingsButton = contentView
+                    .requireViewById(com.android.internal.R.id.button1);
+            languageSettingsButton.setVisibility(View.VISIBLE);
+            languageSettingsButton.setOnClickListener(v -> {
+                v.getContext().startActivity(languageSettingsIntent);
+                hide(displayId, userId);
+            });
+        }
+
+        // Create the current IME subtypes list.
+        final RecyclerView recyclerView = contentView
+                .requireViewById(com.android.internal.R.id.list);
+        recyclerView.setAdapter(new Adapter(items, selectedIndex, inflater, onClickListener));
+        // Scroll to the currently selected IME.
+        recyclerView.scrollToPosition(selectedIndex);
+        // Indicate that the list can be scrolled.
+        recyclerView.setScrollIndicators(
+                hasLanguageSettingsButton ? View.SCROLL_INDICATOR_BOTTOM : 0);
+
+        builder.setOnCancelListener(dialog -> hide(displayId, userId));
+        mMenuItems = items;
+        mDialog = builder.create();
+        mDialog.setCanceledOnTouchOutside(true);
+        final Window w = mDialog.getWindow();
+        w.setHideOverlayWindows(true);
+        final WindowManager.LayoutParams attrs = w.getAttributes();
+        // Use an alternate token for the dialog for that window manager can group the token
+        // with other IME windows based on type vs. grouping based on whichever token happens
+        // to get selected by the system later on.
+        attrs.token = dialogWindowContext.getWindowContextToken();
+        attrs.gravity = Gravity.getAbsoluteGravity(Gravity.BOTTOM | Gravity.END,
+                dialogWindowContext.getResources().getConfiguration().getLayoutDirection());
+        attrs.x = HORIZONTAL_OFFSET;
+        attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+        attrs.type = WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+        // Used for debugging only, not user visible.
+        attrs.setTitle(WINDOW_TITLE);
+        w.setAttributes(attrs);
+
+        mDialog.show();
+        InputMethodManagerInternal.get().updateShouldShowImeSwitcher(displayId, userId);
+    }
+
+    /**
+     * Hides the Input Method Switcher Menu.
+     *
+     * @param displayId the ID of the display from where the menu should be hidden.
+     * @param userId    the ID of the user for which the menu should be hidden.
+     */
+    void hide(int displayId, @UserIdInt int userId) {
+        if (DEBUG) Slog.v(TAG, "Hide IME switcher menu.");
+
+        mMenuItems = null;
+        // Cannot use dialog.isShowing() here, as the cancel listener flow already resets mShowing.
+        if (mDialog != null) {
+            mDialog.dismiss();
+            mDialog = null;
+
+            InputMethodManagerInternal.get().updateShouldShowImeSwitcher(displayId, userId);
+        }
+    }
+
+    /**
+     * Returns whether the Input Method Switcher Menu is showing.
+     */
+    boolean isShowing() {
+        return mDialog != null && mDialog.isShowing();
+    }
+
+    void dump(@NonNull Printer pw, @NonNull String prefix) {
+        final boolean showing = isShowing();
+        pw.println(prefix + "  isShowing: " + showing);
+
+        if (showing) {
+            pw.println(prefix + "  menuItems: " + mMenuItems);
+        }
+    }
+
+    /**
+     * Item to be shown in the Input Method Switcher Menu, containing an input method and
+     * optionally an input method subtype.
+     */
+    static class MenuItem {
+
+        /** The name of the input method. */
+        @NonNull
+        private final CharSequence mImeName;
+
+        /**
+         * The name of the input method subtype, or {@code null} if this item doesn't have a
+         * subtype.
+         */
+        @Nullable
+        private final CharSequence mSubtypeName;
+
+        /** The info of the input method. */
+        @NonNull
+        private final InputMethodInfo mImi;
+
+        /**
+         * The index of the subtype in the input method's array of subtypes,
+         * or {@link InputMethodUtils#NOT_A_SUBTYPE_ID} if this item doesn't have a subtype.
+         */
+        @IntRange(from = NOT_A_SUBTYPE_ID)
+        private final int mSubtypeId;
+
+        /** Whether this item has a group header (only the first item of each input method). */
+        private final boolean mHasHeader;
+
+        /**
+         * Whether this item should has a group divider (same as {@link #mHasHeader},
+         * excluding the first IME).
+         */
+        private final boolean mHasDivider;
+
+        MenuItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
+                @NonNull InputMethodInfo imi, @IntRange(from = NOT_A_SUBTYPE_ID) int subtypeId,
+                boolean hasHeader, boolean hasDivider) {
+            mImeName = imeName;
+            mSubtypeName = subtypeName;
+            mImi = imi;
+            mSubtypeId = subtypeId;
+            mHasHeader = hasHeader;
+            mHasDivider = hasDivider;
+        }
+
+        @Override
+        public String toString() {
+            return "MenuItem{"
+                    + "mImeName=" + mImeName
+                    + " mSubtypeName=" + mSubtypeName
+                    + " mSubtypeId=" + mSubtypeId
+                    + " mHasHeader=" + mHasHeader
+                    + " mHasDivider=" + mHasDivider
+                    + "}";
+        }
+    }
+
+    private static class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
+
+        /** The list of items to show. */
+        @NonNull
+        private final List<MenuItem> mItems;
+        /** The index of the selected item. */
+        private final int mSelectedIndex;
+        @NonNull
+        private final LayoutInflater mInflater;
+        @NonNull
+        private final DialogInterface.OnClickListener mOnClickListener;
+
+        Adapter(@NonNull List<MenuItem> items, int selectedIndex,
+                @NonNull LayoutInflater inflater,
+                @NonNull DialogInterface.OnClickListener onClickListener) {
+            mItems = items;
+            mSelectedIndex = selectedIndex;
+            mInflater = inflater;
+            mOnClickListener = onClickListener;
+        }
+
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            final View view = mInflater.inflate(
+                    com.android.internal.R.layout.input_method_switch_item_new, parent, false);
+
+            return new ViewHolder(view, mOnClickListener);
+        }
+
+        @Override
+        public void onBindViewHolder(ViewHolder holder, int position) {
+            holder.bind(mItems.get(position), position == mSelectedIndex /* isSelected */);
+        }
+
+        @Override
+        public int getItemCount() {
+            return mItems.size();
+        }
+
+        private static class ViewHolder extends RecyclerView.ViewHolder {
+
+            /** The container of the item. */
+            @NonNull
+            private final View mContainer;
+            /** The name of the item. */
+            @NonNull
+            private final TextView mName;
+            /** Indicator for the selected status of the item. */
+            @NonNull
+            private final ImageView mCheckmark;
+            /** The group header optionally drawn above the item. */
+            @NonNull
+            private final TextView mHeader;
+            /** The group divider optionally drawn above the item. */
+            @NonNull
+            private final View mDivider;
+
+            private ViewHolder(@NonNull View itemView,
+                    @NonNull DialogInterface.OnClickListener onClickListener) {
+                super(itemView);
+
+                mContainer = itemView.requireViewById(com.android.internal.R.id.list_item);
+                mName = itemView.requireViewById(com.android.internal.R.id.text);
+                mCheckmark = itemView.requireViewById(com.android.internal.R.id.image);
+                mHeader = itemView.requireViewById(com.android.internal.R.id.header_text);
+                mDivider = itemView.requireViewById(com.android.internal.R.id.divider);
+
+                mContainer.setOnClickListener((v) ->
+                        onClickListener.onClick(null /* dialog */, getAdapterPosition()));
+            }
+
+            /**
+             * Binds the given item to the current view.
+             *
+             * @param item       the item to bind.
+             * @param isSelected whether this is selected.
+             */
+            private void bind(@NonNull MenuItem item, boolean isSelected) {
+                // Use the IME name for subtypes with an empty subtype name.
+                final var name = TextUtils.isEmpty(item.mSubtypeName)
+                        ? item.mImeName : item.mSubtypeName;
+                mContainer.setActivated(isSelected);
+                // Activated is the correct state, but we also set selected for accessibility info.
+                mContainer.setSelected(isSelected);
+                mName.setSelected(isSelected);
+                mName.setText(name);
+                mCheckmark.setVisibility(isSelected ? View.VISIBLE : View.GONE);
+                mHeader.setText(item.mImeName);
+                mHeader.setVisibility(item.mHasHeader ? View.VISIBLE : View.GONE);
+                mDivider.setVisibility(item.mHasDivider ? View.VISIBLE : View.GONE);
+            }
+        }
+    }
+}