Merge "Polish RemoteInputView defocus animation" into tm-qpr-dev

This commit is contained in:
Johannes Gallmann
2023-01-11 13:01:58 +00:00
committed by Android (Google) Code Review
3 changed files with 105 additions and 52 deletions

View File

@@ -65,8 +65,6 @@ import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.util.DumpUtilsKt; import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.ListenerSet; import com.android.systemui.util.ListenerSet;
import dagger.Lazy;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -74,6 +72,8 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import dagger.Lazy;
/** /**
* Class for handling remote input state over a set of notifications. This class handles things * Class for handling remote input state over a set of notifications. This class handles things
* like keeping notifications temporarily that were cancelled as a response to a remote input * like keeping notifications temporarily that were cancelled as a response to a remote input
@@ -465,8 +465,7 @@ public class NotificationRemoteInputManager implements Dumpable {
riv.getController().setRemoteInput(input); riv.getController().setRemoteInput(input);
riv.getController().setRemoteInputs(inputs); riv.getController().setRemoteInputs(inputs);
riv.getController().setEditedSuggestionInfo(editedSuggestionInfo); riv.getController().setEditedSuggestionInfo(editedSuggestionInfo);
ViewGroup parent = view.getParent() != null ? (ViewGroup) view.getParent() : null; riv.focusAnimated();
riv.focusAnimated(parent);
if (userMessageContent != null) { if (userMessageContent != null) {
riv.setEditTextContent(userMessageContent); riv.setEditTextContent(userMessageContent);
} }

View File

@@ -109,6 +109,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private static final long FOCUS_ANIMATION_FADE_IN_DELAY = 33; private static final long FOCUS_ANIMATION_FADE_IN_DELAY = 33;
private static final long FOCUS_ANIMATION_FADE_IN_DURATION = 83; private static final long FOCUS_ANIMATION_FADE_IN_DURATION = 83;
private static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f; private static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f;
private static final long DEFOCUS_ANIMATION_FADE_OUT_DELAY = 120;
private static final long DEFOCUS_ANIMATION_CROSSFADE_DELAY = 180;
public final Object mToken = new Object(); public final Object mToken = new Object();
@@ -421,7 +423,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
} }
@VisibleForTesting @VisibleForTesting
void onDefocus(boolean animate, boolean logClose) { void onDefocus(boolean animate, boolean logClose, @Nullable Runnable doAfterDefocus) {
mController.removeRemoteInput(mEntry, mToken); mController.removeRemoteInput(mEntry, mToken);
mEntry.remoteInputText = mEditText.getText(); mEntry.remoteInputText = mEditText.getText();
@@ -431,18 +433,20 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
ViewGroup parent = (ViewGroup) getParent(); ViewGroup parent = (ViewGroup) getParent();
if (animate && parent != null && mIsFocusAnimationFlagActive) { if (animate && parent != null && mIsFocusAnimationFlagActive) {
ViewGroup grandParent = (ViewGroup) parent.getParent(); ViewGroup grandParent = (ViewGroup) parent.getParent();
ViewGroupOverlay overlay = parent.getOverlay(); ViewGroupOverlay overlay = parent.getOverlay();
View actionsContainer = getActionsContainerLayout();
int actionsContainerHeight =
actionsContainer != null ? actionsContainer.getHeight() : 0;
// After adding this RemoteInputView to the overlay of the parent (and thus removing // After adding this RemoteInputView to the overlay of the parent (and thus removing
// it from the parent itself), the parent will shrink in height. This causes the // it from the parent itself), the parent will shrink in height. This causes the
// overlay to be moved. To correct the position of the overlay we need to offset it. // overlay to be moved. To correct the position of the overlay we need to offset it.
int overlayOffsetY = getMaxSiblingHeight() - getHeight(); int overlayOffsetY = actionsContainerHeight - getHeight();
overlay.add(this); overlay.add(this);
if (grandParent != null) grandParent.setClipChildren(false); if (grandParent != null) grandParent.setClipChildren(false);
Animator animator = getDefocusAnimator(overlayOffsetY); Animator animator = getDefocusAnimator(actionsContainer, overlayOffsetY);
View self = this; View self = this;
animator.addListener(new AnimatorListenerAdapter() { animator.addListener(new AnimatorListenerAdapter() {
@Override @Override
@@ -454,8 +458,12 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
if (mWrapper != null) { if (mWrapper != null) {
mWrapper.setRemoteInputVisible(false); mWrapper.setRemoteInputVisible(false);
} }
if (doAfterDefocus != null) {
doAfterDefocus.run();
}
} }
}); });
if (actionsContainer != null) actionsContainer.setAlpha(0f);
animator.start(); animator.start();
} else if (animate && mRevealParams != null && mRevealParams.radius > 0) { } else if (animate && mRevealParams != null && mRevealParams.radius > 0) {
@@ -474,6 +482,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
reveal.start(); reveal.start();
} else { } else {
setVisibility(GONE); setVisibility(GONE);
if (doAfterDefocus != null) doAfterDefocus.run();
if (mWrapper != null) { if (mWrapper != null) {
mWrapper.setRemoteInputVisible(false); mWrapper.setRemoteInputVisible(false);
} }
@@ -596,10 +605,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
/** /**
* Focuses the RemoteInputView and animates its appearance * Focuses the RemoteInputView and animates its appearance
*
* @param crossFadeView view that will be crossfaded during the appearance animation
*/ */
public void focusAnimated(View crossFadeView) { public void focusAnimated() {
if (!mIsFocusAnimationFlagActive && getVisibility() != VISIBLE if (!mIsFocusAnimationFlagActive && getVisibility() != VISIBLE
&& mRevealParams != null) { && mRevealParams != null) {
android.animation.Animator animator = mRevealParams.createCircularRevealAnimator(this); android.animation.Animator animator = mRevealParams.createCircularRevealAnimator(this);
@@ -609,7 +616,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
} else if (mIsFocusAnimationFlagActive && getVisibility() != VISIBLE) { } else if (mIsFocusAnimationFlagActive && getVisibility() != VISIBLE) {
mIsAnimatingAppearance = true; mIsAnimatingAppearance = true;
setAlpha(0f); setAlpha(0f);
Animator focusAnimator = getFocusAnimator(crossFadeView); Animator focusAnimator = getFocusAnimator(getActionsContainerLayout());
focusAnimator.addListener(new AnimatorListenerAdapter() { focusAnimator.addListener(new AnimatorListenerAdapter() {
@Override @Override
public void onAnimationEnd(Animator animation, boolean isReverse) { public void onAnimationEnd(Animator animation, boolean isReverse) {
@@ -661,6 +668,23 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
} }
private void reset() { private void reset() {
if (mIsFocusAnimationFlagActive) {
mProgressBar.setVisibility(INVISIBLE);
mResetting = true;
mSending = false;
onDefocus(true /* animate */, false /* logClose */, () -> {
mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
mEditText.getText().clear();
mEditText.setEnabled(isAggregatedVisible());
mSendButton.setVisibility(VISIBLE);
mController.removeSpinning(mEntry.getKey(), mToken);
updateSendButton();
setAttachment(null);
mResetting = false;
});
return;
}
mResetting = true; mResetting = true;
mSending = false; mSending = false;
mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText()); mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
@@ -671,7 +695,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mProgressBar.setVisibility(INVISIBLE); mProgressBar.setVisibility(INVISIBLE);
mController.removeSpinning(mEntry.getKey(), mToken); mController.removeSpinning(mEntry.getKey(), mToken);
updateSendButton(); updateSendButton();
onDefocus(false /* animate */, false /* logClose */); onDefocus(false /* animate */, false /* logClose */, null /* doAfterDefocus */);
setAttachment(null); setAttachment(null);
mResetting = false; mResetting = false;
@@ -825,23 +849,22 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
} }
/** /**
* @return max sibling height (0 in case of no siblings) * @return action button container view (i.e. ViewGroup containing Reply button etc.)
*/ */
public int getMaxSiblingHeight() { public View getActionsContainerLayout() {
ViewGroup parentView = (ViewGroup) getParent(); ViewGroup parentView = (ViewGroup) getParent();
int maxHeight = 0; if (parentView == null) return null;
if (parentView == null) return 0; return parentView.findViewById(com.android.internal.R.id.actions_container_layout);
for (int i = 0; i < parentView.getChildCount(); i++) {
View siblingView = parentView.getChildAt(i);
if (siblingView != this) maxHeight = Math.max(maxHeight, siblingView.getHeight());
}
return maxHeight;
} }
/** /**
* Creates an animator for the focus animation. * Creates an animator for the focus animation.
*
* @param fadeOutView View that will be faded out during the focus animation.
*/ */
private Animator getFocusAnimator(View crossFadeView) { private Animator getFocusAnimator(@Nullable View fadeOutView) {
final AnimatorSet animatorSet = new AnimatorSet();
final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f); final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f);
alphaAnimator.setStartDelay(FOCUS_ANIMATION_FADE_IN_DELAY); alphaAnimator.setStartDelay(FOCUS_ANIMATION_FADE_IN_DELAY);
alphaAnimator.setDuration(FOCUS_ANIMATION_FADE_IN_DURATION); alphaAnimator.setDuration(FOCUS_ANIMATION_FADE_IN_DURATION);
@@ -854,30 +877,36 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION); scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN); scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
final Animator crossFadeViewAlphaAnimator = if (fadeOutView == null) {
ObjectAnimator.ofFloat(crossFadeView, View.ALPHA, 1f, 0f); animatorSet.playTogether(alphaAnimator, scaleAnimator);
crossFadeViewAlphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION); } else {
crossFadeViewAlphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR); final Animator fadeOutViewAlphaAnimator =
alphaAnimator.addListener(new AnimatorListenerAdapter() { ObjectAnimator.ofFloat(fadeOutView, View.ALPHA, 1f, 0f);
@Override fadeOutViewAlphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION);
public void onAnimationEnd(Animator animation, boolean isReverse) { fadeOutViewAlphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
crossFadeView.setAlpha(1f); animatorSet.addListener(new AnimatorListenerAdapter() {
} @Override
}); public void onAnimationEnd(Animator animation, boolean isReverse) {
fadeOutView.setAlpha(1f);
final AnimatorSet animatorSet = new AnimatorSet(); }
animatorSet.playTogether(alphaAnimator, scaleAnimator, crossFadeViewAlphaAnimator); });
animatorSet.playTogether(alphaAnimator, scaleAnimator, fadeOutViewAlphaAnimator);
}
return animatorSet; return animatorSet;
} }
/** /**
* Creates an animator for the defocus animation. * Creates an animator for the defocus animation.
* *
* @param offsetY The RemoteInputView will be offset by offsetY during the animation * @param fadeInView View that will be faded in during the defocus animation.
* @param offsetY The RemoteInputView will be offset by offsetY during the animation
*/ */
private Animator getDefocusAnimator(int offsetY) { private Animator getDefocusAnimator(@Nullable View fadeInView, int offsetY) {
final AnimatorSet animatorSet = new AnimatorSet();
final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f); final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f);
alphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION); alphaAnimator.setDuration(FOCUS_ANIMATION_FADE_IN_DURATION);
alphaAnimator.setStartDelay(DEFOCUS_ANIMATION_FADE_OUT_DELAY);
alphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR); alphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1f, FOCUS_ANIMATION_MIN_SCALE); ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1f, FOCUS_ANIMATION_MIN_SCALE);
@@ -893,8 +922,17 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
} }
}); });
final AnimatorSet animatorSet = new AnimatorSet(); if (fadeInView == null) {
animatorSet.playTogether(alphaAnimator, scaleAnimator); animatorSet.playTogether(alphaAnimator, scaleAnimator);
} else {
fadeInView.forceHasOverlappingRendering(false);
Animator fadeInViewAlphaAnimator =
ObjectAnimator.ofFloat(fadeInView, View.ALPHA, 0f, 1f);
fadeInViewAlphaAnimator.setDuration(FOCUS_ANIMATION_FADE_IN_DURATION);
fadeInViewAlphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
fadeInViewAlphaAnimator.setStartDelay(DEFOCUS_ANIMATION_CROSSFADE_DELAY);
animatorSet.playTogether(alphaAnimator, scaleAnimator, fadeInViewAlphaAnimator);
}
return animatorSet; return animatorSet;
} }
@@ -1011,7 +1049,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
if (isFocusable() && isEnabled()) { if (isFocusable() && isEnabled()) {
setInnerFocusable(false); setInnerFocusable(false);
if (mRemoteInputView != null) { if (mRemoteInputView != null) {
mRemoteInputView.onDefocus(animate, true /* logClose */); mRemoteInputView
.onDefocus(animate, true /* logClose */, null /* doAfterDefocus */);
} }
mShowImeOnInputConnection = false; mShowImeOnInputConnection = false;
} }

View File

@@ -390,19 +390,27 @@ public class RemoteInputViewTest extends SysuiTestCase {
bindController(view, row.getEntry()); bindController(view, row.getEntry());
view.setVisibility(View.GONE); view.setVisibility(View.GONE);
View crossFadeView = new View(mContext); View fadeOutView = new View(mContext);
fadeOutView.setId(com.android.internal.R.id.actions_container_layout);
FrameLayout parent = new FrameLayout(mContext);
parent.addView(view);
parent.addView(fadeOutView);
// Start focus animation // Start focus animation
view.focusAnimated(crossFadeView); view.focusAnimated();
assertTrue(view.isAnimatingAppearance()); assertTrue(view.isAnimatingAppearance());
// fast forward to end of animation // fast forward to 1 ms before end of animation and verify fadeOutView has alpha set to 0f
mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD); mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD - 1);
assertEquals(0f, fadeOutView.getAlpha());
// assert that crossFadeView's alpha is reset to 1f after the animation (hidden behind // fast forward to end of animation
mAnimatorTestRule.advanceTimeBy(1);
// assert that fadeOutView's alpha is reset to 1f after the animation (hidden behind
// RemoteInputView) // RemoteInputView)
assertEquals(1f, crossFadeView.getAlpha()); assertEquals(1f, fadeOutView.getAlpha());
assertFalse(view.isAnimatingAppearance()); assertFalse(view.isAnimatingAppearance());
assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(View.VISIBLE, view.getVisibility());
assertEquals(1f, view.getAlpha()); assertEquals(1f, view.getAlpha());
@@ -415,20 +423,27 @@ public class RemoteInputViewTest extends SysuiTestCase {
mDependency, mDependency,
TestableLooper.get(this)); TestableLooper.get(this));
ExpandableNotificationRow row = helper.createRow(); ExpandableNotificationRow row = helper.createRow();
FrameLayout remoteInputViewParent = new FrameLayout(mContext);
RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController); RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
remoteInputViewParent.addView(view);
bindController(view, row.getEntry()); bindController(view, row.getEntry());
View fadeInView = new View(mContext);
fadeInView.setId(com.android.internal.R.id.actions_container_layout);
FrameLayout parent = new FrameLayout(mContext);
parent.addView(view);
parent.addView(fadeInView);
// Start defocus animation // Start defocus animation
view.onDefocus(true, false); view.onDefocus(true /* animate */, false /* logClose */, null /* doAfterDefocus */);
assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(View.VISIBLE, view.getVisibility());
assertEquals(0f, fadeInView.getAlpha());
// fast forward to end of animation // fast forward to end of animation
mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD); mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD);
// assert that RemoteInputView is no longer visible // assert that RemoteInputView is no longer visible
assertEquals(View.GONE, view.getVisibility()); assertEquals(View.GONE, view.getVisibility());
assertEquals(1f, fadeInView.getAlpha());
} }
// NOTE: because we're refactoring the RemoteInputView and moving logic into the // NOTE: because we're refactoring the RemoteInputView and moving logic into the