Merge changes from topic "inline-reply-animation" into tm-qpr-dev

* changes:
  Fix headsup notification RemoteInputView appearance animation
  Fix notification inline reply animation
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index a5608af..7aef82a 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -27,6 +27,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:gravity="end"
+        android:layout_gravity="bottom"
         android:orientation="horizontal"
         android:background="@color/notification_action_list_background_color"
         >
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index e624441..25fb24a 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -221,6 +221,7 @@
         "WindowManager-Shell",
         "LowLightDreamLib",
         "motion_tool_lib",
+        "androidx.core_core-animation-testing-nodeps",
     ],
 }
 
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
index a5b2f80..f4b0a45 100644
--- a/packages/SystemUI/res/layout/remote_input.xml
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -20,6 +20,7 @@
 <com.android.systemui.statusbar.policy.RemoteInputView
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/remote_input"
+        android:forceHasOverlappingRendering="false"
         android:layout_height="match_parent"
         android:layout_width="match_parent">
     <LinearLayout
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 21d75dc..339fd13 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -103,6 +103,11 @@
     // TODO(b/257315550): Tracking Bug
     val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when", teamfood = true)
 
+    // TODO(b/260335638): Tracking Bug
+    @JvmField
+    val NOTIFICATION_INLINE_REPLY_ANIMATION =
+        unreleasedFlag(174148361, "notification_inline_reply_animation", teamfood = true)
+
     val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
         unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index f668528..3516037 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -463,7 +463,11 @@
         riv.getController().setRemoteInput(input);
         riv.getController().setRemoteInputs(inputs);
         riv.getController().setEditedSuggestionInfo(editedSuggestionInfo);
-        riv.focusAnimated();
+        ViewGroup parent = view.getParent() != null ? (ViewGroup) view.getParent() : null;
+        if (parent != null) {
+            riv.setDefocusTargetHeight(parent.getHeight());
+        }
+        riv.focusAnimated(parent);
         if (userMessageContent != null) {
             riv.setEditTextContent(userMessageContent);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 47cdde4..56eb4b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.inflation;
 
+import static com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
@@ -30,6 +31,7 @@
 
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -71,6 +73,7 @@
     private NotificationListContainer mListContainer;
     private BindRowCallback mBindRowCallback;
     private NotificationClicker mNotificationClicker;
+    private FeatureFlags mFeatureFlags;
 
     @Inject
     public NotificationRowBinderImpl(
@@ -82,7 +85,8 @@
             RowContentBindStage rowContentBindStage,
             Provider<RowInflaterTask> rowInflaterTaskProvider,
             ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder,
-            IconManager iconManager) {
+            IconManager iconManager,
+            FeatureFlags featureFlags) {
         mContext = context;
         mNotifBindPipeline = notifBindPipeline;
         mRowContentBindStage = rowContentBindStage;
@@ -92,6 +96,7 @@
         mRowInflaterTaskProvider = rowInflaterTaskProvider;
         mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder;
         mIconManager = iconManager;
+        mFeatureFlags = featureFlags;
     }
 
     /**
@@ -176,6 +181,8 @@
         entry.setRow(row);
         mNotifBindPipeline.manageRow(entry, row);
         mBindRowCallback.onBindRow(row);
+        row.setInlineReplyAnimationFlagEnabled(
+                mFeatureFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION));
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 44a231d..c1173e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -284,6 +284,7 @@
     private View.OnClickListener mOnAppClickListener;
     private View.OnClickListener mOnFeedbackClickListener;
     private Path mExpandingClipPath;
+    private boolean mIsInlineReplyAnimationFlagEnabled = false;
 
     // Listener will be called when receiving a long click event.
     // Use #setLongPressPosition to optionally assign positional data with the long press.
@@ -3079,6 +3080,10 @@
         return 0;
     }
 
+    public void setInlineReplyAnimationFlagEnabled(boolean isEnabled) {
+        mIsInlineReplyAnimationFlagEnabled = isEnabled;
+    }
+
     @Override
     public void setActualHeight(int height, boolean notifyListeners) {
         boolean changed = height != getActualHeight();
@@ -3098,7 +3103,11 @@
         }
         int contentHeight = Math.max(getMinHeight(), height);
         for (NotificationContentView l : mLayouts) {
-            l.setContentHeight(contentHeight);
+            if (mIsInlineReplyAnimationFlagEnabled) {
+                l.setContentHeight(height);
+            } else {
+                l.setContentHeight(contentHeight);
+            }
         }
         if (mIsSummaryWithChildren) {
             mChildrenContainer.setActualHeight(height);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 277ad8e..e46bf52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -70,7 +70,7 @@
 
 /**
  * A frame layout containing the actual payload of the notification, including the contracted,
- * expanded and heads up layout. This class is responsible for clipping the content and and
+ * expanded and heads up layout. This class is responsible for clipping the content and
  * switching between the expanded, contracted and the heads up view depending on its clipped size.
  */
 public class NotificationContentView extends FrameLayout implements NotificationFadeAware {
@@ -627,6 +627,13 @@
         int hint;
         if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
             hint = getViewHeight(VISIBLE_TYPE_HEADSUP);
+            if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance()
+                    && mHeadsUpRemoteInputController.isFocusAnimationFlagActive()) {
+                // While the RemoteInputView is animating its appearance, it should be allowed
+                // to overlap the hint, therefore no space is reserved for the hint during the
+                // appearance animation of the RemoteInputView
+                hint = 0;
+            }
         } else if (mExpandedChild != null) {
             hint = getViewHeight(VISIBLE_TYPE_EXPANDED);
         } else if (mContractedChild != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index dd400b3..d8a8c5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -18,8 +18,8 @@
 
 import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
+import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_STANDARD;
+
 import android.app.ActivityManager;
 import android.app.Notification;
 import android.content.Context;
@@ -57,6 +57,7 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
+import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -67,6 +68,11 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.core.animation.Animator;
+import androidx.core.animation.AnimatorListenerAdapter;
+import androidx.core.animation.AnimatorSet;
+import androidx.core.animation.ObjectAnimator;
+import androidx.core.animation.ValueAnimator;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
@@ -74,6 +80,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.animation.InterpolatorsAndroidX;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
@@ -97,6 +104,12 @@
     // A marker object that let's us easily find views of this class.
     public static final Object VIEW_TAG = new Object();
 
+    private static final long FOCUS_ANIMATION_TOTAL_DURATION = ANIMATION_DURATION_STANDARD;
+    private static final long FOCUS_ANIMATION_CROSSFADE_DURATION = 50;
+    private static final long FOCUS_ANIMATION_FADE_IN_DELAY = 33;
+    private static final long FOCUS_ANIMATION_FADE_IN_DURATION = 83;
+    private static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f;
+
     public final Object mToken = new Object();
 
     private final SendButtonTextWatcher mTextWatcher;
@@ -108,6 +121,7 @@
 
     private RemoteEditText mEditText;
     private ImageButton mSendButton;
+    private LinearLayout mContentView;
     private GradientDrawable mContentBackground;
     private ProgressBar mProgressBar;
     private ImageView mDelete;
@@ -115,7 +129,10 @@
     private boolean mColorized;
     private int mTint;
     private boolean mResetting;
-    @Nullable private RevealParams mRevealParams;
+    @Nullable
+    private RevealParams mRevealParams;
+    private Rect mContentBackgroundBounds;
+    private boolean mIsFocusAnimationFlagActive;
 
     // TODO(b/193539698): move these to a Controller
     private RemoteInputController mController;
@@ -125,6 +142,10 @@
     private boolean mSending;
     private NotificationViewWrapper mWrapper;
 
+    private Integer mDefocusTargetHeight = null;
+    private boolean mIsAnimatingAppearance = false;
+
+
     // TODO(b/193539698): remove this; views shouldn't have access to their controller, and places
     //  that need the controller shouldn't have access to the view
     private RemoteInputViewController mViewController;
@@ -255,8 +276,8 @@
         mDeleteBg.setImageTintBlendMode(BlendMode.SRC_IN);
         mDelete.setImageTintBlendMode(BlendMode.SRC_IN);
         mDelete.setOnClickListener(v -> setAttachment(null));
-        LinearLayout contentView = findViewById(R.id.remote_input_content);
-        contentView.setBackground(mContentBackground);
+        mContentView = findViewById(R.id.remote_input_content);
+        mContentView.setBackground(mContentBackground);
         mEditText = findViewById(R.id.remote_input_text);
         mEditText.setInnerFocusable(false);
         // TextView initializes the spell checked when the view is attached to a window.
@@ -398,20 +419,74 @@
         return true;
     }
 
-    private void onDefocus(boolean animate, boolean logClose) {
+    public boolean isAnimatingAppearance() {
+        return mIsAnimatingAppearance;
+    }
+
+    /**
+     * View will ensure to use at most the provided defocusTargetHeight, when defocusing animated.
+     * This is to ensure that the parent can resize itself to the targetHeight while the defocus
+     * animation of the RemoteInputView is running.
+     *
+     * @param defocusTargetHeight The target height the parent will resize itself to. If null, the
+     *                            RemoteInputView will not resize itself.
+     */
+    public void setDefocusTargetHeight(Integer defocusTargetHeight) {
+        mDefocusTargetHeight = defocusTargetHeight;
+    }
+
+    @VisibleForTesting
+    void onDefocus(boolean animate, boolean logClose) {
         mController.removeRemoteInput(mEntry, mToken);
         mEntry.remoteInputText = mEditText.getText();
 
         // During removal, we get reattached and lose focus. Not hiding in that
         // case to prevent flicker.
         if (!mRemoved) {
-            if (animate && mRevealParams != null && mRevealParams.radius > 0) {
-                Animator reveal = mRevealParams.createCircularHideAnimator(this);
-                reveal.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
-                reveal.setDuration(StackStateAnimator.ANIMATION_DURATION_CLOSE_REMOTE_INPUT);
-                reveal.addListener(new AnimatorListenerAdapter() {
+            if (animate && mIsFocusAnimationFlagActive) {
+                Animator animator = getDefocusAnimator();
+
+                // When defocusing, the notification needs to shrink. Therefore, we need to free
+                // up the space that is needed for the RemoteInputView. This is done by setting
+                // a negative top margin of the height difference of the RemoteInputView and its
+                // sibling (the actions_container_layout containing the Reply button)
+                if (mDefocusTargetHeight != null && mDefocusTargetHeight < getHeight()
+                        && mDefocusTargetHeight >= 0
+                        && getLayoutParams() instanceof FrameLayout.LayoutParams) {
+                    int heightToShrink = getHeight() - mDefocusTargetHeight;
+                    FrameLayout.LayoutParams layoutParams =
+                            (FrameLayout.LayoutParams) getLayoutParams();
+                    layoutParams.topMargin = -heightToShrink;
+                    setLayoutParams(layoutParams);
+                    ((ViewGroup) getParent().getParent()).setClipChildren(false);
+                }
+
+                animator.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
+                        //reset top margin after the animation
+                        if (getLayoutParams() instanceof FrameLayout.LayoutParams) {
+                            FrameLayout.LayoutParams layoutParams =
+                                    (FrameLayout.LayoutParams) getLayoutParams();
+                            layoutParams.topMargin = 0;
+                            setLayoutParams(layoutParams);
+                            ((ViewGroup) getParent().getParent()).setClipChildren(true);
+                        }
+                        setVisibility(GONE);
+                        if (mWrapper != null) {
+                            mWrapper.setRemoteInputVisible(false);
+                        }
+                    }
+                });
+                animator.start();
+
+            } else if (animate && mRevealParams != null && mRevealParams.radius > 0) {
+                android.animation.Animator reveal = mRevealParams.createCircularHideAnimator(this);
+                reveal.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+                reveal.setDuration(StackStateAnimator.ANIMATION_DURATION_CLOSE_REMOTE_INPUT);
+                reveal.addListener(new android.animation.AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(android.animation.Animator animation) {
                         setVisibility(GONE);
                         if (mWrapper != null) {
                             mWrapper.setRemoteInputVisible(false);
@@ -533,12 +608,37 @@
         mEditText.setText(editTextContent);
     }
 
-    public void focusAnimated() {
-        if (getVisibility() != VISIBLE && mRevealParams != null) {
-            Animator animator = mRevealParams.createCircularRevealAnimator(this);
+    /**
+     * Sets whether the feature flag for the updated inline reply animation is active or not.
+     * @param active
+     */
+    public void setIsFocusAnimationFlagActive(boolean active) {
+        mIsFocusAnimationFlagActive = active;
+    }
+
+    /**
+     * Focuses the RemoteInputView and animates its appearance
+     *
+     * @param crossFadeView view that will be crossfaded during the appearance animation
+     */
+    public void focusAnimated(View crossFadeView) {
+        if (!mIsFocusAnimationFlagActive && getVisibility() != VISIBLE
+                && mRevealParams != null) {
+            android.animation.Animator animator = mRevealParams.createCircularRevealAnimator(this);
             animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
             animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
             animator.start();
+        } else if (mIsFocusAnimationFlagActive && getVisibility() != VISIBLE) {
+            mIsAnimatingAppearance = true;
+            setAlpha(0f);
+            Animator focusAnimator = getFocusAnimator(crossFadeView);
+            focusAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation, boolean isReverse) {
+                    mIsAnimatingAppearance = false;
+                }
+            });
+            focusAnimator.start();
         }
         focus();
     }
@@ -737,6 +837,81 @@
         mOnSendListeners.remove(listener);
     }
 
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        if (mIsFocusAnimationFlagActive) setPivotY(getMeasuredHeight());
+        if (mContentBackgroundBounds != null) {
+            mContentBackground.setBounds(mContentBackgroundBounds);
+        }
+    }
+
+    private Animator getFocusAnimator(View crossFadeView) {
+        final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f);
+        alphaAnimator.setStartDelay(FOCUS_ANIMATION_FADE_IN_DELAY);
+        alphaAnimator.setDuration(FOCUS_ANIMATION_FADE_IN_DURATION);
+        alphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
+
+        ValueAnimator scaleAnimator = ValueAnimator.ofFloat(FOCUS_ANIMATION_MIN_SCALE, 1f);
+        scaleAnimator.addUpdateListener(valueAnimator -> {
+            setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue());
+        });
+        scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
+        scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
+
+        final Animator crossFadeViewAlphaAnimator =
+                ObjectAnimator.ofFloat(crossFadeView, View.ALPHA, 1f, 0f);
+        crossFadeViewAlphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION);
+        crossFadeViewAlphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
+        alphaAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation, boolean isReverse) {
+                crossFadeView.setAlpha(1f);
+            }
+        });
+
+        final AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.playTogether(alphaAnimator, scaleAnimator, crossFadeViewAlphaAnimator);
+        return animatorSet;
+    }
+
+    private Animator getDefocusAnimator() {
+        final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f);
+        alphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION);
+        alphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
+
+        ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1f, FOCUS_ANIMATION_MIN_SCALE);
+        scaleAnimator.addUpdateListener(valueAnimator -> {
+            setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue());
+        });
+        scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
+        scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
+        scaleAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation, boolean isReverse) {
+                setFocusAnimationScaleY(1f);
+            }
+        });
+
+        final AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.playTogether(alphaAnimator, scaleAnimator);
+        return animatorSet;
+    }
+
+    /**
+     * Sets affected view properties for a vertical scale animation
+     *
+     * @param scaleY desired vertical view scale
+     */
+    private void setFocusAnimationScaleY(float scaleY) {
+        int verticalBoundOffset = (int) ((1f - scaleY) * 0.5f * mContentView.getHeight());
+        mContentBackgroundBounds = new Rect(0, verticalBoundOffset, mContentView.getWidth(),
+                mContentView.getHeight() - verticalBoundOffset);
+        mContentBackground.setBounds(mContentBackgroundBounds);
+        mContentView.setBackground(mContentBackground);
+        setTranslationY(verticalBoundOffset);
+    }
+
     /** Handler for button click on send action in IME. */
     private class EditorActionHandler implements TextView.OnEditorActionListener {
 
@@ -991,11 +1166,11 @@
             this.radius = radius;
         }
 
-        Animator createCircularHideAnimator(View view) {
+        android.animation.Animator createCircularHideAnimator(View view) {
             return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, radius, 0);
         }
 
-        Animator createCircularRevealAnimator(View view) {
+        android.animation.Animator createCircularRevealAnimator(View view) {
             return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, 0, radius);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index f845101..22b4c9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -30,6 +30,8 @@
 import android.view.View
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION
 import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.RemoteInputController
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -61,6 +63,8 @@
 
     var revealParams: RevealParams?
 
+    val isFocusAnimationFlagActive: Boolean
+
     /**
      * Sets the smart reply that should be inserted in the remote input, or `null` if the user is
      * not editing a smart reply.
@@ -117,7 +121,8 @@
     private val remoteInputQuickSettingsDisabler: RemoteInputQuickSettingsDisabler,
     private val remoteInputController: RemoteInputController,
     private val shortcutManager: ShortcutManager,
-    private val uiEventLogger: UiEventLogger
+    private val uiEventLogger: UiEventLogger,
+    private val mFlags: FeatureFlags
 ) : RemoteInputViewController {
 
     private val onSendListeners = ArraySet<OnSendRemoteInputListener>()
@@ -149,6 +154,9 @@
 
     override val isActive: Boolean get() = view.isActive
 
+    override val isFocusAnimationFlagActive: Boolean
+        get() = mFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION)
+
     override fun bind() {
         if (isBound) return
         isBound = true
@@ -159,6 +167,7 @@
             view.setSupportedMimeTypes(it.allowedDataTypes)
         }
         view.setRevealParameters(revealParams)
+        view.setIsFocusAnimationFlagActive(isFocusAnimationFlagActive)
 
         view.addOnEditTextFocusChangedListener(onFocusChangeListener)
         view.addOnSendRemoteInputListener(onSendRemoteInputListener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 915e999..2c47204 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -16,10 +16,14 @@
 
 import static android.view.ContentInfo.SOURCE_CLIPBOARD;
 
+import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_STANDARD;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -57,6 +61,7 @@
 import android.window.WindowOnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
+import androidx.core.animation.AnimatorTestRule;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
@@ -64,15 +69,19 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.LightBarController;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -99,6 +108,9 @@
     private BlockingQueueIntentReceiver mReceiver;
     private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
 
+    @ClassRule
+    public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+
     @Before
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
@@ -294,6 +306,9 @@
         /* invoke the captured callback */
         onBackInvokedCallbackCaptor.getValue().onBackInvoked();
 
+        /* wait for RemoteInputView disappear animation to finish */
+        mAnimatorTestRule.advanceTimeBy(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+
         /* verify that the RemoteInputView goes away */
         assertEquals(view.getVisibility(), View.GONE);
     }
@@ -363,19 +378,73 @@
                 mUiEventLoggerFake.eventId(1));
     }
 
+    @Test
+    public void testFocusAnimation() throws Exception {
+        NotificationTestHelper helper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
+        ExpandableNotificationRow row = helper.createRow();
+        RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+        bindController(view, row.getEntry());
+        view.setVisibility(View.GONE);
+
+        View crossFadeView = new View(mContext);
+
+        // Start focus animation
+        view.focusAnimated(crossFadeView);
+
+        assertTrue(view.isAnimatingAppearance());
+
+        // fast forward to end of animation
+        mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD);
+
+        // assert that crossFadeView's alpha is reset to 1f after the animation (hidden behind
+        // RemoteInputView)
+        assertEquals(1f, crossFadeView.getAlpha());
+        assertFalse(view.isAnimatingAppearance());
+        assertEquals(View.VISIBLE, view.getVisibility());
+        assertEquals(1f, view.getAlpha());
+    }
+
+    @Test
+    public void testDefocusAnimation() throws Exception {
+        NotificationTestHelper helper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
+        ExpandableNotificationRow row = helper.createRow();
+        RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+        bindController(view, row.getEntry());
+
+        // Start defocus animation
+        view.onDefocus(true, false);
+        assertEquals(View.VISIBLE, view.getVisibility());
+
+        // fast forward to end of animation
+        mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD);
+
+        // assert that RemoteInputView is no longer visible
+        assertEquals(View.GONE, view.getVisibility());
+    }
+
     // NOTE: because we're refactoring the RemoteInputView and moving logic into the
-    //  RemoteInputViewController, it's easiest to just test the system of the two classes together.
+    // RemoteInputViewController, it's easiest to just test the system of the two classes together.
     @NonNull
     private RemoteInputViewController bindController(
             RemoteInputView view,
             NotificationEntry entry) {
+        FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
+        fakeFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true);
         RemoteInputViewControllerImpl viewController = new RemoteInputViewControllerImpl(
                 view,
                 entry,
                 mRemoteInputQuickSettingsDisabler,
                 mController,
                 mShortcutManager,
-                mUiEventLoggerFake);
+                mUiEventLoggerFake,
+                fakeFeatureFlags
+                );
         viewController.bind();
         return viewController;
     }