Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5f97d3fd authored by Jamie Garside's avatar Jamie Garside
Browse files

Make bouncer handedness animation interruptible.

This makes the bouncer motion interruptible. In general, the motion
looks "fine" when interrupting the bouncer, but the opacity was always
run from 100% -> 0% -> 100%, regardless of the initial state. This
change remembers the initial opacity for the fade out, so we don't get
as much jank when cancelling the animation.

This is fine when the bouncer is fading back in, but leads to a bit of
weirdness if you manage the interrupt the bouncer before it has fully
faded out (it still does the correct amount of travel, so it fires
off-screen, then comes back on again). Still, this is *extremely*
difficult to do; the user has to do two taps within 100ms to do this.

Demonstration videos:

Normal speed:
https://drive.google.com/file/d/1FRPysuA-eHNo_2nwMXnXKXwpBrJeS-we/view?usp=sharing&resourcekey=0-3-JzHRhvUJ9ciXClL6dOBQ
5x anim scale:
https://drive.google.com/file/d/16xr6VZ_KrLAs8sLCxrqAxaZsEQugi6UN/view?usp=sharing&resourcekey=0-5cPAHfX9Xy3_vDVEfP4Blg

Bug: 195012405
Test: Manually tested. See videos.
Change-Id: I42534f65b2669c5da4cadaaaf8a25409edd260c6
parent ea5b316f
Loading
Loading
Loading
Loading
+23 −10
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;

import static java.lang.Integer.max;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.AlertDialog;
@@ -35,7 +37,6 @@ import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewPropertyAnimator;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.WindowManager;
@@ -43,6 +44,7 @@ import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
@@ -111,7 +113,7 @@ public class KeyguardSecurityContainer extends FrameLayout {

    private boolean mIsSecurityViewLeftAligned = true;
    private boolean mOneHandedMode = false;
    private ViewPropertyAnimator mRunningOneHandedAnimator;
    @Nullable private ValueAnimator mRunningOneHandedAnimator;

    private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
            new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
@@ -347,9 +349,9 @@ public class KeyguardSecurityContainer extends FrameLayout {
            Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN;
            Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;

            ValueAnimator anim = ValueAnimator.ofFloat(0.0f, 1.0f);
            anim.setDuration(BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS);
            anim.setInterpolator(Interpolators.LINEAR);
            mRunningOneHandedAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
            mRunningOneHandedAnimator.setDuration(BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS);
            mRunningOneHandedAnimator.setInterpolator(Interpolators.LINEAR);

            int initialTranslation = (int) mSecurityViewFlipper.getTranslationX();
            int totalTranslation = (int) getResources().getDimension(
@@ -361,7 +363,15 @@ public class KeyguardSecurityContainer extends FrameLayout {
                mSecurityViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null);
            }

            anim.addUpdateListener(animation -> {
            float initialAlpha = mSecurityViewFlipper.getAlpha();

            mRunningOneHandedAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    mRunningOneHandedAnimator = null;
                }
            });
            mRunningOneHandedAnimator.addUpdateListener(animation -> {
                float switchPoint = BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION;
                boolean isFadingOut = animation.getAnimatedFraction() < switchPoint;

@@ -378,13 +388,16 @@ public class KeyguardSecurityContainer extends FrameLayout {
                if (isFadingOut) {
                    // The bouncer fades out over the first X%.
                    float fadeOutFraction = MathUtils.constrainedMap(
                            /* rangeMin= */0.0f,
                            /* rangeMax= */1.0f,
                            /* rangeMin= */1.0f,
                            /* rangeMax= */0.0f,
                            /* valueMin= */0.0f,
                            /* valueMax= */switchPoint,
                            animation.getAnimatedFraction());
                    float opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction);
                    mSecurityViewFlipper.setAlpha(1f - opacity);

                    // When fading out, the alpha needs to start from the initial opacity of the
                    // view flipper, otherwise we get a weird bit of jank as it ramps back to 100%.
                    mSecurityViewFlipper.setAlpha(opacity * initialAlpha);

                    // Animate away from the source.
                    mSecurityViewFlipper.setTranslationX(initialTranslation + currentTranslation);
@@ -409,7 +422,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
                }
            });

            anim.start();
            mRunningOneHandedAnimator.start();
        } else {
            mSecurityViewFlipper.setTranslationX(targetTranslation);
        }