Merge "Handle the clear all button in the refactored stack." into main
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
index de334bb..2338be2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
@@ -85,7 +85,9 @@
 
     public void setFooterVisibility(@Visibility int visibility) {
         mFooterVisibility = visibility;
-        setSecondaryVisible(visibility == View.VISIBLE, false);
+        setSecondaryVisible(/* visible = */ visibility == View.VISIBLE,
+                /* animate = */false,
+                /* onAnimationEnded = */ null);
     }
 
     public void setFooterText(@StringRes int text) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 19bce89..fa2748c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
@@ -41,6 +42,7 @@
     private val groupExpansionManagerImpl: GroupExpansionManagerImpl,
     private val notificationIconAreaController: NotificationIconAreaController,
     private val renderListInteractor: RenderNotificationListInteractor,
+    private val activeNotificationsInteractor: ActiveNotificationsInteractor,
 ) : Coordinator {
 
     override fun attach(pipeline: NotifPipeline) {
@@ -50,7 +52,13 @@
 
     fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
         traceSection("StackCoordinator.onAfterRenderList") {
-            controller.setNotifStats(calculateNotifStats(entries))
+            val notifStats = calculateNotifStats(entries)
+            if (FooterViewRefactor.isEnabled) {
+                activeNotificationsInteractor.setNotifStats(notifStats)
+            }
+            // TODO(b/293167744): This shouldn't be done if the footer flag is on, once the footer
+            //  visibility is handled in the new stack.
+            controller.setNotifStats(notifStats)
             if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) {
                 renderListInteractor.setRenderedList(entries)
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
index 12ee54d..5ed82cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar.notification.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore.Key
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
@@ -37,6 +38,9 @@
 
     /** Are any already-seen notifications currently filtered out of the active list? */
     val hasFilteredOutSeenNotifications = MutableStateFlow(false)
+
+    /** Stats about the list of notifications attached to the shade */
+    val notifStats = MutableStateFlow(NotifStats.empty)
 }
 
 /** Represents the notification list, comprised of groups and individual notifications. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 542f3c4..31893b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -15,6 +15,7 @@
 
 package com.android.systemui.statusbar.notification.domain.interactor
 
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
@@ -52,4 +53,14 @@
      */
     val areAnyNotificationsPresentValue: Boolean
         get() = repository.activeNotifications.value.renderList.isNotEmpty()
+
+    /** Are there are any notifications that can be cleared by the "Clear all" button? */
+    val hasClearableNotifications: Flow<Boolean> =
+        repository.notifStats
+            .map { it.hasClearableAlertingNotifs || it.hasClearableSilentNotifs }
+            .distinctUntilChanged()
+
+    fun setNotifStats(notifStats: NotifStats) {
+        repository.notifStats.value = notifStats
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 10a43d5..3184d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -46,6 +46,7 @@
 import com.android.systemui.util.DumpUtilsKt;
 
 import java.io.PrintWriter;
+import java.util.function.Consumer;
 
 public class FooterView extends StackScrollerDecorView {
     private static final String TAG = "FooterView";
@@ -63,9 +64,13 @@
     private String mSeenNotifsFilteredText;
     private Drawable mSeenNotifsFilteredIcon;
 
+    private @StringRes int mClearAllButtonTextId;
+    private @StringRes int mClearAllButtonDescriptionId;
     private @StringRes int mMessageStringId;
     private @DrawableRes int mMessageIconId;
 
+    private OnClickListener mClearAllButtonClickListener;
+
     public FooterView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -84,12 +89,18 @@
         return isSecondaryVisible();
     }
 
+    /** See {@link this#setClearAllButtonVisible(boolean, boolean, Consumer)}. */
+    public void setClearAllButtonVisible(boolean visible, boolean animate) {
+        setClearAllButtonVisible(visible, animate, /* onAnimationEnded = */ null);
+    }
+
     /**
      * Set the visibility of the "Clear all" button to {@code visible}. Animate the change if
      * {@code animate} is true.
      */
-    public void setClearAllButtonVisible(boolean visible, boolean animate) {
-        setSecondaryVisible(visible, animate);
+    public void setClearAllButtonVisible(boolean visible, boolean animate,
+            Consumer<Boolean> onAnimationEnded) {
+        setSecondaryVisible(visible, animate, onAnimationEnded);
     }
 
     @Override
@@ -106,6 +117,42 @@
         });
     }
 
+    /** Set the text label for the "Clear all" button. */
+    public void setClearAllButtonText(@StringRes int textId) {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+        if (mClearAllButtonTextId == textId) {
+            return; // nothing changed
+        }
+        mClearAllButtonTextId = textId;
+        updateClearAllButtonText();
+    }
+
+    private void updateClearAllButtonText() {
+        if (mClearAllButtonTextId == 0) {
+            return; // not initialized yet
+        }
+        mClearAllButton.setText(getContext().getString(mClearAllButtonTextId));
+    }
+
+    /** Set the accessibility content description for the "Clear all" button. */
+    public void setClearAllButtonDescription(@StringRes int contentDescriptionId) {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            return;
+        }
+        if (mClearAllButtonDescriptionId == contentDescriptionId) {
+            return; // nothing changed
+        }
+        mClearAllButtonDescriptionId = contentDescriptionId;
+        updateClearAllButtonDescription();
+    }
+
+    private void updateClearAllButtonDescription() {
+        if (mClearAllButtonDescriptionId == 0) {
+            return; // not initialized yet
+        }
+        mClearAllButton.setContentDescription(getContext().getString(mClearAllButtonDescriptionId));
+    }
+
     /** Set the string for a message to be shown instead of the buttons. */
     public void setMessageString(@StringRes int messageId) {
         if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
@@ -181,6 +228,10 @@
 
     /** Set onClickListener for the clear all (end) button. */
     public void setClearAllButtonClickListener(OnClickListener listener) {
+        if (FooterViewRefactor.isEnabled()) {
+            if (mClearAllButtonClickListener == listener) return;
+            mClearAllButtonClickListener = listener;
+        }
         mClearAllButton.setOnClickListener(listener);
     }
 
@@ -214,7 +265,28 @@
             mManageButton.setText(mManageNotificationText);
             mManageButton.setContentDescription(mManageNotificationText);
         }
-        if (!FooterViewRefactor.isEnabled()) {
+        if (FooterViewRefactor.isEnabled()) {
+            updateClearAllButtonText();
+            updateClearAllButtonDescription();
+
+            updateMessageString();
+            updateMessageIcon();
+        } else {
+            // NOTE: Prior to the refactor, `updateResources` set the class properties to the right
+            // string values. It was always being called together with `updateContent`, which
+            // deals with actually associating those string values with the correct views
+            // (buttons or text).
+            // In the new code, the resource IDs are being set in the view binder (through
+            // setMessageString and similar setters). The setters themselves now deal with
+            // updating both the resource IDs and the views where appropriate (as in, calling
+            // `updateMessageString` when the resource ID changes). This eliminates the need for
+            // `updateResources`, which will eventually be removed. There are, however, still
+            // situations in which we want to update the views even if the resource IDs didn't
+            // change, such as configuration changes.
+            mClearAllButton.setText(R.string.clear_all_notifications_text);
+            mClearAllButton.setContentDescription(
+                    mContext.getString(R.string.accessibility_clear_all));
+
             mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
             mSeenNotifsFooterTextView
                     .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null);
@@ -230,16 +302,8 @@
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         updateColors();
-        mClearAllButton.setText(R.string.clear_all_notifications_text);
-        mClearAllButton.setContentDescription(
-                mContext.getString(R.string.accessibility_clear_all));
         updateResources();
         updateContent();
-
-        if (FooterViewRefactor.isEnabled()) {
-            updateMessageString();
-            updateMessageIcon();
-        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index 6d823437..0299114 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -16,10 +16,14 @@
 
 package com.android.systemui.statusbar.notification.footer.ui.viewbinder
 
+import android.view.View
 import androidx.lifecycle.lifecycleScope
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
+import com.android.systemui.util.ui.value
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.launch
 
@@ -28,9 +32,31 @@
     fun bind(
         footer: FooterView,
         viewModel: FooterViewModel,
+        clearAllNotifications: View.OnClickListener,
     ): DisposableHandle {
+        // Listen for changes when the view is attached.
         return footer.repeatWhenAttached {
-            // Listen for changes when the view is attached.
+            lifecycleScope.launch {
+                viewModel.clearAllButton.collect { button ->
+                    if (button.isVisible.isAnimating) {
+                        footer.setClearAllButtonVisible(
+                            button.isVisible.value,
+                            /* animate = */ true,
+                        ) { _ ->
+                            button.isVisible.stopAnimating()
+                        }
+                    } else {
+                        footer.setClearAllButtonVisible(
+                            button.isVisible.value,
+                            /* animate = */ false,
+                        )
+                    }
+                    footer.setClearAllButtonText(button.labelId)
+                    footer.setClearAllButtonDescription(button.accessibilityDescriptionId)
+                    footer.setClearAllButtonClickListener(clearAllNotifications)
+                }
+            }
+
             lifecycleScope.launch {
                 viewModel.message.collect { message ->
                     footer.setFooterLabelVisible(message.visible)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
new file mode 100644
index 0000000..ea5abef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.footer.ui.viewmodel
+
+import android.annotation.StringRes
+import com.android.systemui.util.ui.AnimatedValue
+
+data class FooterButtonViewModel(
+    @StringRes val labelId: Int,
+    @StringRes val accessibilityDescriptionId: Int,
+    val isVisible: AnimatedValue<Boolean>,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index 3d68a7b..721bea1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -18,18 +18,51 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.ui.AnimatableEvent
+import com.android.systemui.util.ui.toAnimatedValueFlow
 import dagger.Module
 import dagger.Provides
 import java.util.Optional
 import javax.inject.Provider
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 
 /** ViewModel for [FooterView]. */
-class FooterViewModel(seenNotificationsInteractor: SeenNotificationsInteractor) {
+class FooterViewModel(
+    activeNotificationsInteractor: ActiveNotificationsInteractor,
+    seenNotificationsInteractor: SeenNotificationsInteractor,
+    shadeInteractor: ShadeInteractor,
+) {
+    val clearAllButton: Flow<FooterButtonViewModel> =
+        activeNotificationsInteractor.hasClearableNotifications
+            .sample(
+                combine(
+                        shadeInteractor.isShadeFullyExpanded,
+                        shadeInteractor.isShadeTouchable,
+                        ::Pair
+                    )
+                    .onStart { emit(Pair(false, false)) }
+            ) { hasClearableNotifications, (isShadeFullyExpanded, animationsEnabled) ->
+                val shouldAnimate = isShadeFullyExpanded && animationsEnabled
+                AnimatableEvent(hasClearableNotifications, shouldAnimate)
+            }
+            .toAnimatedValueFlow()
+            .map { visible ->
+                FooterButtonViewModel(
+                    labelId = R.string.clear_all_notifications_text,
+                    accessibilityDescriptionId = R.string.accessibility_clear_all,
+                    isVisible = visible,
+                )
+            }
+
     val message: Flow<FooterMessageViewModel> =
         seenNotificationsInteractor.hasFilteredOutSeenNotifications.map { hasFilteredOutNotifs ->
             FooterMessageViewModel(
@@ -45,10 +78,18 @@
     @Provides
     @SysUISingleton
     fun provideOptional(
+        activeNotificationsInteractor: Provider<ActiveNotificationsInteractor>,
         seenNotificationsInteractor: Provider<SeenNotificationsInteractor>,
+        shadeInteractor: Provider<ShadeInteractor>,
     ): Optional<FooterViewModel> {
         return if (FooterViewRefactor.isEnabled) {
-            Optional.of(FooterViewModel(seenNotificationsInteractor.get()))
+            Optional.of(
+                FooterViewModel(
+                    activeNotificationsInteractor.get(),
+                    seenNotificationsInteractor.get(),
+                    shadeInteractor.get()
+                )
+            )
         } else {
             Optional.empty()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index ec90a8d..162e8af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -54,7 +54,7 @@
         mContent = findContentView();
         mSecondaryView = findSecondaryView();
         setVisible(false /* visible */, false /* animate */);
-        setSecondaryVisible(false /* visible */, false /* animate */);
+        setSecondaryVisible(false /* visible */, false /* animate */, null /* onAnimationEnd */);
         setOutlineProvider(null);
     }
 
@@ -155,15 +155,23 @@
     /**
      * Set the secondary view of this layout to visible.
      *
-     * @param visible should the secondary view be visible
-     * @param animate should the change be animated
+     * @param visible          True if the contents should be visible.
+     * @param animate          True if we should fade to new visibility.
+     * @param onAnimationEnded Callback to run after visibility updates, takes a boolean as a
+     *                         parameter that represents whether the animation was cancelled.
      */
-    protected void setSecondaryVisible(boolean visible, boolean animate) {
+    protected void setSecondaryVisible(boolean visible, boolean animate,
+            Consumer<Boolean> onAnimationEnded) {
         if (mIsSecondaryVisible != visible) {
             mSecondaryAnimating = animate;
             mIsSecondaryVisible = visible;
-            setViewVisible(mSecondaryView, visible, animate,
-                    (cancelled) -> onSecondaryVisibilityAnimationEnd());
+            Consumer<Boolean> onAnimationEndedWrapper = (cancelled) -> {
+                onContentVisibilityAnimationEnd();
+                if (onAnimationEnded != null) {
+                    onAnimationEnded.accept(cancelled);
+                }
+            };
+            setViewVisible(mSecondaryView, visible, animate, onAnimationEndedWrapper);
         }
 
         if (!mSecondaryAnimating) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 6a34f98..283a593 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -4608,13 +4608,15 @@
         if (mManageButtonClickListener != null) {
             mFooterView.setManageButtonClickListener(mManageButtonClickListener);
         }
-        mFooterView.setClearAllButtonClickListener(v -> {
-            if (mFooterClearAllListener != null) {
-                mFooterClearAllListener.onClearAll();
-            }
-            clearNotifications(ROWS_ALL, true /* closeShade */);
-            footerView.setClearAllButtonVisible(false /* visible */, true /* animate */);
-        });
+        if (!FooterViewRefactor.isEnabled()) {
+            mFooterView.setClearAllButtonClickListener(v -> {
+                if (mFooterClearAllListener != null) {
+                    mFooterClearAllListener.onClearAll();
+                }
+                clearNotifications(ROWS_ALL, true /* closeShade */);
+                footerView.setClearAllButtonVisible(false /* visible */, true /* animate */);
+            });
+        }
         if (FooterViewRefactor.isEnabled()) {
             updateFooter();
         }
@@ -4687,9 +4689,9 @@
         }
         boolean animate = mIsExpanded && mAnimationsEnabled;
         mFooterView.setVisible(visible, animate);
-        mFooterView.setClearAllButtonVisible(showDismissView, animate);
         mFooterView.showHistory(showHistory);
         if (!FooterViewRefactor.isEnabled()) {
+            mFooterView.setClearAllButtonVisible(showDismissView, animate);
             mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
         }
     }
@@ -5355,11 +5357,15 @@
         return viewsToRemove;
     }
 
+    /** Clear all clearable notifications when the user requests it. */
+    public void clearAllNotifications() {
+        clearNotifications(ROWS_ALL, /* closeShade = */ true);
+    }
+
     /**
      * Collects a list of visible rows, and animates them away in a staggered fashion as if they
      * were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd.
      */
-    @VisibleForTesting
     void clearNotifications(@SelectedRows int selection, boolean closeShade) {
         // Animate-swipe all dismissable notifications, then animate the shade closed
         final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection);
@@ -5659,6 +5665,7 @@
     }
 
     void setFooterClearAllListener(FooterClearAllListener listener) {
+        FooterViewRefactor.assertInLegacyMode();
         mFooterClearAllListener = listener;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index d5ec0c5..e6315fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -470,7 +470,7 @@
 
                 @Override
                 public void onSnooze(StatusBarNotification sbn,
-                                     NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
+                        NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
                     mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
                 }
 
@@ -592,7 +592,7 @@
 
                 @Override
                 public boolean updateSwipeProgress(View animView, boolean dismissable,
-                                                   float swipeProgress) {
+                        float swipeProgress) {
                     // Returning true prevents alpha fading.
                     return false;
                 }
@@ -759,8 +759,10 @@
         mView.setClearAllAnimationListener(this::onAnimationEnd);
         mView.setClearAllListener((selection) -> mUiEventLogger.log(
                 NotificationPanelEvent.fromSelection(selection)));
-        mView.setFooterClearAllListener(() ->
-                mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES));
+        if (!FooterViewRefactor.isEnabled()) {
+            mView.setFooterClearAllListener(() ->
+                    mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES));
+        }
         mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive());
         mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() {
             @Override
@@ -1090,7 +1092,7 @@
     }
 
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
-                                    boolean cancelAnimators) {
+            boolean cancelAnimators) {
         mView.setOverScrollAmount(amount, onTop, animate, cancelAnimators);
     }
 
@@ -1408,14 +1410,14 @@
      * Return whether there are any clearable notifications
      */
     public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
-        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the clear all
-        //  button in the refactored code
+        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer
+        //  visibility in the refactored code
         return hasNotifications(selection, true /* clearable */);
     }
 
     public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
-        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the clear all
-        //  button in the refactored code
+        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer
+        //  visibility in the refactored code
         boolean hasAlertingMatchingClearable = isClearable
                 ? mNotifStats.getHasClearableAlertingNotifs()
                 : mNotifStats.getHasNonClearableAlertingNotifs();
@@ -1454,7 +1456,7 @@
     public RemoteInputController.Delegate createDelegate() {
         return new RemoteInputController.Delegate() {
             public void setRemoteInputActive(NotificationEntry entry,
-                                             boolean remoteInputActive) {
+                    boolean remoteInputActive) {
                 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
                 entry.notifyHeightChanged(true /* needsAnimation */);
                 updateFooter();
@@ -1573,7 +1575,7 @@
     }
 
     private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove,
-                                @SelectedRows int selectedRows) {
+            @SelectedRows int selectedRows) {
         if (selectedRows == ROWS_ALL) {
             mNotifCollection.dismissAllNotifications(
                     mLockscreenUserManager.getCurrentUserId());
@@ -1665,7 +1667,7 @@
      * Set rounded rect clipping bounds on this view.
      */
     public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
-                                         int bottomRadius) {
+            int bottomRadius) {
         mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius);
     }
 
@@ -2021,7 +2023,7 @@
     private class NotifStackControllerImpl implements NotifStackController {
         @Override
         public void setNotifStats(@NonNull NotifStats notifStats) {
-            // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once clear all visibility
+            // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once footer visibility
             // is handled in the refactored stack.
             mNotifStats = notifStats;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 54df4ab..4554085 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -19,6 +19,8 @@
 import android.view.LayoutInflater
 import androidx.lifecycle.lifecycleScope
 import com.android.app.tracing.traceSection
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.nano.MetricsProto
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.common.ui.reinflateAndBindLatest
 import com.android.systemui.common.ui.view.setImportantForAccessibilityYesNo
@@ -46,6 +48,7 @@
 @Inject
 constructor(
     private val viewModel: NotificationListViewModel,
+    private val metricsLogger: MetricsLogger,
     private val configuration: ConfigurationState,
     private val configurationController: ConfigurationController,
     private val falsingManager: FalsingManager,
@@ -100,7 +103,17 @@
                     attachToRoot = false,
                 ) { footerView: FooterView ->
                     traceSection("bind FooterView") {
-                        val disposableHandle = FooterViewBinder.bind(footerView, footerViewModel)
+                        val disposableHandle =
+                            FooterViewBinder.bind(
+                                footerView,
+                                footerViewModel,
+                                clearAllNotifications = {
+                                    metricsLogger.action(
+                                        MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES
+                                    )
+                                    parentView.clearAllNotifications()
+                                },
+                            )
                         parentView.setFooterView(footerView)
                         return@reinflateAndBindLatest disposableHandle
                     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index 428574b..fa5fad0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
@@ -57,6 +58,7 @@
     @Mock private lateinit var groupExpansionManagerImpl: GroupExpansionManagerImpl
     @Mock private lateinit var notificationIconAreaController: NotificationIconAreaController
     @Mock private lateinit var renderListInteractor: RenderNotificationListInteractor
+    @Mock private lateinit var activeNotificationsInteractor: ActiveNotificationsInteractor
     @Mock private lateinit var stackController: NotifStackController
     @Mock private lateinit var section: NotifSection
 
@@ -75,6 +77,7 @@
                 groupExpansionManagerImpl,
                 notificationIconAreaController,
                 renderListInteractor,
+                activeNotificationsInteractor,
             )
         coordinator.attach(pipeline)
         afterRenderListListener = withArgCaptor {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
index b24cafd..4ab3cd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -72,4 +72,58 @@
             assertThat(areAnyNotificationsPresent).isFalse()
             assertThat(underTest.areAnyNotificationsPresentValue).isFalse()
         }
+
+    @Test
+    fun testHasClearableNotifications_whenHasClearableAlertingNotifs() =
+        testComponent.runTest {
+            val hasClearable by collectLastValue(underTest.hasClearableNotifications)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = true,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = false,
+                )
+            runCurrent()
+
+            assertThat(hasClearable).isTrue()
+        }
+
+    @Test
+    fun testHasClearableNotifications_whenHasClearableSilentNotifs() =
+        testComponent.runTest {
+            val hasClearable by collectLastValue(underTest.hasClearableNotifications)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = true,
+                )
+            runCurrent()
+
+            assertThat(hasClearable).isTrue()
+        }
+
+    @Test
+    fun testHasClearableNotifications_whenHasNoClearableNotifs() =
+        testComponent.runTest {
+            val hasClearable by collectLastValue(underTest.hasClearableNotifications)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = false,
+                )
+            runCurrent()
+
+            assertThat(hasClearable).isFalse()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index a64ac67..22c5bae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -114,9 +114,46 @@
     }
 
     @Test
+    public void testSetClearAllButtonText_resourceOnlyFetchedOnce() {
+        int resId = R.string.clear_all_notifications_text;
+        mView.setClearAllButtonText(resId);
+        verify(mSpyContext).getString(eq(resId));
+
+        clearInvocations(mSpyContext);
+
+        assertThat(((TextView) mView.findViewById(R.id.dismiss_text))
+                .getText().toString()).contains("Clear all");
+
+        // Set it a few more times, it shouldn't lead to the resource being fetched again
+        mView.setClearAllButtonText(resId);
+        mView.setClearAllButtonText(resId);
+
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
+    public void testSetClearAllButtonDescription_resourceOnlyFetchedOnce() {
+        int resId = R.string.accessibility_clear_all;
+        mView.setClearAllButtonDescription(resId);
+        verify(mSpyContext).getString(eq(resId));
+
+        clearInvocations(mSpyContext);
+
+        assertThat(((TextView) mView.findViewById(R.id.dismiss_text))
+                .getContentDescription().toString()).contains("Clear all notifications");
+
+        // Set it a few more times, it shouldn't lead to the resource being fetched again
+        mView.setClearAllButtonDescription(resId);
+        mView.setClearAllButtonDescription(resId);
+
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
     public void testSetMessageString_resourceOnlyFetchedOnce() {
-        mView.setMessageString(R.string.unlock_to_see_notif_text);
-        verify(mSpyContext).getString(eq(R.string.unlock_to_see_notif_text));
+        int resId = R.string.unlock_to_see_notif_text;
+        mView.setMessageString(resId);
+        verify(mSpyContext).getString(eq(resId));
 
         clearInvocations(mSpyContext);
 
@@ -124,22 +161,23 @@
                 .getText().toString()).contains("Unlock");
 
         // Set it a few more times, it shouldn't lead to the resource being fetched again
-        mView.setMessageString(R.string.unlock_to_see_notif_text);
-        mView.setMessageString(R.string.unlock_to_see_notif_text);
+        mView.setMessageString(resId);
+        mView.setMessageString(resId);
 
         verify(mSpyContext, never()).getString(anyInt());
     }
 
     @Test
     public void testSetMessageIcon_resourceOnlyFetchedOnce() {
-        mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
-        verify(mSpyContext).getDrawable(eq(R.drawable.ic_friction_lock_closed));
+        int resId = R.drawable.ic_friction_lock_closed;
+        mView.setMessageIcon(resId);
+        verify(mSpyContext).getDrawable(eq(resId));
 
         clearInvocations(mSpyContext);
 
         // Set it a few more times, it shouldn't lead to the resource being fetched again
-        mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
-        mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
+        mView.setMessageIcon(resId);
+        mView.setMessageIcon(resId);
 
         verify(mSpyContext, never()).getDrawable(anyInt());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 57a7c3c..94dcf7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -18,37 +18,222 @@
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysUITestComponent
+import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.TestMocksModule
+import com.android.systemui.collectLastValue
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.runCurrent
+import com.android.systemui.runTest
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.value
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
+import dagger.BindsInstance
+import dagger.Component
+import java.util.Optional
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class FooterViewModelTest : SysuiTestCase() {
-    private val repository = ActiveNotificationListRepository()
-    private val interactor = SeenNotificationsInteractor(repository)
-    private val underTest = FooterViewModel(interactor)
+    private lateinit var footerViewModel: FooterViewModel
 
-    @Test
-    fun testMessageVisible_whenFilteredNotifications() = runTest {
-        val message by collectLastValue(underTest.message)
+    @SysUISingleton
+    @Component(
+        modules =
+            [
+                SysUITestModule::class,
+                ActivatableNotificationViewModelModule::class,
+                FooterViewModelModule::class,
+                HeadlessSystemUserModeModule::class,
+            ]
+    )
+    interface TestComponent : SysUITestComponent<Optional<FooterViewModel>> {
+        val activeNotificationListRepository: ActiveNotificationListRepository
+        val configurationRepository: FakeConfigurationRepository
+        val keyguardRepository: FakeKeyguardRepository
+        val keyguardTransitionRepository: FakeKeyguardTransitionRepository
+        val shadeRepository: FakeShadeRepository
+        val powerRepository: FakePowerRepository
 
-        repository.hasFilteredOutSeenNotifications.value = true
+        @Component.Factory
+        interface Factory {
+            fun create(
+                @BindsInstance test: SysuiTestCase,
+                featureFlags: FakeFeatureFlagsClassicModule,
+                mocks: TestMocksModule,
+            ): TestComponent
+        }
+    }
 
-        assertThat(message?.visible).isTrue()
+    private val dozeParameters: DozeParameters = mock()
+
+    private val testComponent: TestComponent =
+        DaggerFooterViewModelTest_TestComponent.factory()
+            .create(
+                test = this,
+                featureFlags =
+                    FakeFeatureFlagsClassicModule {
+                        set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
+                    },
+                mocks =
+                    TestMocksModule(
+                        dozeParameters = dozeParameters,
+                    )
+            )
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR)
+
+        // The underTest in the component is Optional, because that matches the provider we
+        // currently have for the footer view model.
+        footerViewModel = testComponent.underTest.get()
     }
 
     @Test
-    fun testMessageVisible_whenNoFilteredNotifications() = runTest {
-        val message by collectLastValue(underTest.message)
+    fun testMessageVisible_whenFilteredNotifications() =
+        testComponent.runTest {
+            val message by collectLastValue(footerViewModel.message)
 
-        repository.hasFilteredOutSeenNotifications.value = false
+            activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
 
-        assertThat(message?.visible).isFalse()
-    }
+            assertThat(message?.visible).isTrue()
+        }
+
+    @Test
+    fun testMessageVisible_whenNoFilteredNotifications() =
+        testComponent.runTest {
+            val message by collectLastValue(footerViewModel.message)
+
+            activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
+
+            assertThat(message?.visible).isFalse()
+        }
+
+    @Test
+    fun testClearAllButtonVisible_whenHasClearableNotifs() =
+        testComponent.runTest {
+            val button by collectLastValue(footerViewModel.clearAllButton)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = true,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = true,
+                )
+            runCurrent()
+
+            assertThat(button?.isVisible?.value).isTrue()
+        }
+
+    @Test
+    fun testClearAllButtonVisible_whenHasNoClearableNotifs() =
+        testComponent.runTest {
+            val button by collectLastValue(footerViewModel.clearAllButton)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = false,
+                )
+            runCurrent()
+
+            assertThat(button?.isVisible?.value).isFalse()
+        }
+
+    @Test
+    fun testClearAllButtonAnimating_whenShadeExpandedAndTouchable() =
+        testComponent.runTest {
+            val button by collectLastValue(footerViewModel.clearAllButton)
+            runCurrent()
+
+            // WHEN shade is expanded
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            shadeRepository.setLegacyShadeExpansion(1f)
+            // AND QS not expanded
+            shadeRepository.setQsExpansion(0f)
+            // AND device is awake
+            powerRepository.updateWakefulness(
+                rawState = WakefulnessState.AWAKE,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.OTHER,
+            )
+            runCurrent()
+
+            // AND there are clearable notifications
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = true,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = true,
+                )
+            runCurrent()
+
+            // THEN button visibility should animate
+            assertThat(button?.isVisible?.isAnimating).isTrue()
+        }
+
+    @Test
+    fun testClearAllButtonAnimating_whenShadeNotExpanded() =
+        testComponent.runTest {
+            val button by collectLastValue(footerViewModel.clearAllButton)
+            runCurrent()
+
+            // WHEN shade is collapsed
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            shadeRepository.setLegacyShadeExpansion(0f)
+            // AND QS not expanded
+            shadeRepository.setQsExpansion(0f)
+            // AND device is awake
+            powerRepository.updateWakefulness(
+                rawState = WakefulnessState.AWAKE,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.OTHER,
+            )
+            runCurrent()
+
+            // AND there are clearable notifications
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = true,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = true,
+                )
+            runCurrent()
+
+            // THEN button visibility should not animate
+            assertThat(button?.isVisible?.isAnimating).isFalse()
+        }
 }