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

Commit 7c08c261 authored by Jamie Garside's avatar Jamie Garside
Browse files

Update motion for one-handed bouncer.

This is a little awkward to implement properly because of the animation
parameters; see the comment in KeyguardSecurityContainer for more
information.

Video link:
https://drive.google.com/file/d/1EJIZo-yn_Pez3nKgipodQ_QwqyDjXPT8/view?usp=sharing&resourcekey=0-TQWNbg-y6f-OcN_FkrC52w

Test: Manually tested.
Bug: 195012405
Change-Id: I550847d7e47608649422709d96b31b90f0e6467d
parent e5c542e7
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -104,4 +104,11 @@
         allow it to use the whole screen space, 0.6 will allow it to use just under half of the
         screen. -->
    <item name="half_opened_bouncer_height_ratio" type="dimen" format="float">0.0</item>

    <!-- The actual amount of translation that is applied to the bouncer when it animates from one
         side of the screen to the other in one-handed mode. Note that it will always translate from
         the side of the screen to the other (it will "jump" closer to the destination while the
         opacity is zero), but this controls how much motion will actually be applied to it while
         animating. Larger values will cause it to move "faster" while fading out/in. -->
    <dimen name="one_handed_bouncer_move_animation_translation">120dp</dimen>
</resources>
+88 −12
Original line number Diff line number Diff line
@@ -21,8 +21,7 @@ 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;
import android.content.Context;
@@ -40,6 +39,8 @@ import android.view.ViewPropertyAnimator;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;

import androidx.annotation.VisibleForTesting;
@@ -85,6 +86,13 @@ public class KeyguardSecurityContainer extends FrameLayout {

    private static final long IME_DISAPPEAR_DURATION_MS = 125;

    // The duration of the animation to switch bouncer sides.
    private static final long BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS = 500;

    // How much of the switch sides animation should be dedicated to fading the bouncer out. The
    // remainder will fade it back in again.
    private static final float BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION = 0.2f;

    @VisibleForTesting
    KeyguardSecurityViewFlipper mSecurityViewFlipper;
    private AlertDialog mAlertDialog;
@@ -322,18 +330,87 @@ public class KeyguardSecurityContainer extends FrameLayout {
                ? 0 : (int) (getMeasuredWidth() - mSecurityViewFlipper.getWidth());

        if (animate) {
            mRunningOneHandedAnimator =
                    mSecurityViewFlipper.animate().translationX(targetTranslation);
            mRunningOneHandedAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
            mRunningOneHandedAnimator.setListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    mRunningOneHandedAnimator = null;
            // This animation is a bit fun to implement. The bouncer needs to move, and fade in/out
            // at the same time. The issue is, the bouncer should only move a short amount (120dp or
            // so), but obviously needs to go from one side of the screen to the other. This needs a
            // pretty custom animation.
            //
            // This works as follows. It uses a ValueAnimation to simply drive the animation
            // progress. This animator is responsible for both the translation of the bouncer, and
            // the current fade. It will fade the bouncer out while also moving it along the 120dp
            // path. Once the bouncer is fully faded out though, it will "snap" the bouncer closer
            // to its destination, then fade it back in again. The effect is that the bouncer will
            // move from 0 -> X while fading out, then (destination - X) -> destination while fading
            // back in again.
            // TODO(b/195012405): Make this animation properly abortable.
            Interpolator positionInterpolator = AnimationUtils.loadInterpolator(
                    mContext, android.R.interpolator.fast_out_extra_slow_in);
            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);

            int initialTranslation = (int) mSecurityViewFlipper.getTranslationX();
            int totalTranslation = (int) getResources().getDimension(
                    R.dimen.one_handed_bouncer_move_animation_translation);

            final boolean shouldRestoreLayerType = mSecurityViewFlipper.hasOverlappingRendering()
                    && mSecurityViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE;
            if (shouldRestoreLayerType) {
                mSecurityViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null);
            }

            anim.addUpdateListener(animation -> {
                float switchPoint = BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION;
                boolean isFadingOut = animation.getAnimatedFraction() < switchPoint;

                int currentTranslation = (int) (positionInterpolator.getInterpolation(
                        animation.getAnimatedFraction()) * totalTranslation);
                int translationRemaining = totalTranslation - currentTranslation;

                // Flip the sign if we're going from right to left.
                if (mIsSecurityViewLeftAligned) {
                    currentTranslation = -currentTranslation;
                    translationRemaining = -translationRemaining;
                }

                if (isFadingOut) {
                    // The bouncer fades out over the first X%.
                    float fadeOutFraction = MathUtils.constrainedMap(
                            /* rangeMin= */0.0f,
                            /* rangeMax= */1.0f,
                            /* valueMin= */0.0f,
                            /* valueMax= */switchPoint,
                            animation.getAnimatedFraction());
                    float opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction);
                    mSecurityViewFlipper.setAlpha(1f - opacity);

                    // Animate away from the source.
                    mSecurityViewFlipper.setTranslationX(initialTranslation + currentTranslation);
                } else {
                    // And in again over the remaining (100-X)%.
                    float fadeInFraction = MathUtils.constrainedMap(
                            /* rangeMin= */0.0f,
                            /* rangeMax= */1.0f,
                            /* valueMin= */switchPoint,
                            /* valueMax= */1.0f,
                            animation.getAnimatedFraction());

                    float opacity = fadeInInterpolator.getInterpolation(fadeInFraction);
                    mSecurityViewFlipper.setAlpha(opacity);

                    // Fading back in, animate towards the destination.
                    mSecurityViewFlipper.setTranslationX(targetTranslation - translationRemaining);
                }

                if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) {
                    mSecurityViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null);
                }
            });

            mRunningOneHandedAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
            mRunningOneHandedAnimator.start();
            anim.start();
        } else {
            mSecurityViewFlipper.setTranslationX(targetTranslation);
        }
@@ -682,4 +759,3 @@ public class KeyguardSecurityContainer extends FrameLayout {
        mDisappearAnimRunning = false;
    }
}