Fix headsup notification RemoteInputView appearance animation

This CL fixes a bug where the appearance animation of the RemoteInputView in a headsup collapsed notification was slightly broken. The RemoteInputView would do its appearance animation partly outside (below) the notification boundaries. This was because the RemoteInputView was prevented to overlap the hint during the expand animation.

Bug: 174148361
Test: atest RemoteInputViewTest, Manual, i.e. posting various types of Notifications from the Notify2-RVC application with a reply action added to them. Then observing the animations visually and analyzing frames from screen recordings.
Change-Id: I90a637e9b57dea0e365699cef9e088a562301f55
This commit is contained in:
Johannes Gallmann
2022-11-14 09:54:49 +01:00
parent b642af90a1
commit 4b01e37db5
5 changed files with 44 additions and 3 deletions

View File

@@ -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 android.view.ViewGroup;
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 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
private NotificationListContainer mListContainer;
private BindRowCallback mBindRowCallback;
private NotificationClicker mNotificationClicker;
private FeatureFlags mFeatureFlags;
@Inject
public NotificationRowBinderImpl(
@@ -82,7 +85,8 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
RowContentBindStage rowContentBindStage,
Provider<RowInflaterTask> rowInflaterTaskProvider,
ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder,
IconManager iconManager) {
IconManager iconManager,
FeatureFlags featureFlags) {
mContext = context;
mNotifBindPipeline = notifBindPipeline;
mRowContentBindStage = rowContentBindStage;
@@ -92,6 +96,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
mRowInflaterTaskProvider = rowInflaterTaskProvider;
mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder;
mIconManager = iconManager;
mFeatureFlags = featureFlags;
}
/**
@@ -176,6 +181,8 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
entry.setRow(row);
mNotifBindPipeline.manageRow(entry, row);
mBindRowCallback.onBindRow(row);
row.setInlineReplyAnimationFlagEnabled(
mFeatureFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION));
}
/**

View File

@@ -284,6 +284,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
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.
@@ -3073,6 +3074,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return 0;
}
public void setInlineReplyAnimationFlagEnabled(boolean isEnabled) {
mIsInlineReplyAnimationFlagEnabled = isEnabled;
}
@Override
public void setActualHeight(int height, boolean notifyListeners) {
boolean changed = height != getActualHeight();
@@ -3092,7 +3097,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
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);

View File

@@ -627,6 +627,13 @@ public class NotificationContentView extends FrameLayout implements Notification
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) {

View File

@@ -143,6 +143,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
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
@@ -418,6 +419,10 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
return true;
}
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
@@ -624,8 +629,16 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
animator.start();
} else if (mIsFocusAnimationFlagActive && getVisibility() != VISIBLE) {
mIsAnimatingAppearance = true;
setAlpha(0f);
getFocusAnimator(crossFadeView).start();
Animator focusAnimator = getFocusAnimator(crossFadeView);
focusAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
mIsAnimatingAppearance = false;
}
});
focusAnimator.start();
}
focus();
}

View File

@@ -21,7 +21,9 @@ import static com.android.systemui.statusbar.notification.stack.StackStateAnimat
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;
@@ -392,12 +394,15 @@ public class RemoteInputViewTest extends SysuiTestCase {
// 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());
}