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

Commit caec34ff authored by omarmt's avatar omarmt Committed by Omar Miatello
Browse files

BackEvent: add generated events for fling gesture

The system generates additional progress events based on finger speed.
The idea is to capture the energy of the fling gesture and continue the movement adding up to 100ms.

Test: atest BackAnimationControllerTest
Bug: 263402927
Change-Id: I88898435680b22ffbb06e41692bb041b5da03076
parent 766ec400
Loading
Loading
Loading
Loading
+97 −2
Original line number Diff line number Diff line
@@ -20,6 +20,9 @@ import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTas
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityTaskManager;
@@ -37,7 +40,9 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MathUtils;
import android.util.SparseArray;
import android.view.IRemoteAnimationRunner;
import android.view.InputDevice;
@@ -56,6 +61,7 @@ import android.window.IOnBackInvokedCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.view.AppearanceRegion;
import com.android.wm.shell.animation.FlingAnimationUtils;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
@@ -80,6 +86,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
    public static boolean IS_U_ANIMATION_ENABLED =
            SystemProperties.getInt("persist.wm.debug.predictive_back_anim",
                    SETTING_VALUE_ON) == SETTING_VALUE_ON;

    public static final float FLING_MAX_LENGTH_SECONDS = 0.1f;     // 100ms
    public static final float FLING_SPEED_UP_FACTOR = 0.6f;

    /**
     * The maximum additional progress in case of fling gesture.
     * The end animation starts after the user lifts the finger from the screen, we continue to
     * fire {@link BackEvent}s until the velocity reaches 0.
     */
    private static final float MAX_FLING_PROGRESS = 0.3f; /* 30% of the screen */

    /** Predictive back animation developer option */
    private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
    /**
@@ -96,6 +113,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
    private boolean mShouldStartOnNextMoveEvent = false;
    /** @see #setTriggerBack(boolean) */
    private boolean mTriggerBack;
    private FlingAnimationUtils mFlingAnimationUtils;

    @Nullable
    private BackNavigationInfo mBackNavigationInfo;
@@ -174,6 +192,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
        mBgHandler = bgHandler;
        shellInit.addInitCallback(this::onInit, this);
        mAnimationBackground = backAnimationBackground;
        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        mFlingAnimationUtils = new FlingAnimationUtils.Builder(displayMetrics)
                .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
                .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
                .build();
    }

    @VisibleForTesting
@@ -465,6 +488,78 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
        }
    }


    /**
     * Allows us to manage the fling gesture, it smoothly animates the current progress value to
     * the final position, calculated based on the current velocity.
     *
     * @param callback the callback to be invoked when the animation ends.
     */
    private void dispatchOrAnimateOnBackInvoked(IOnBackInvokedCallback callback) {
        if (callback == null) {
            return;
        }

        boolean animationStarted = false;

        if (mBackNavigationInfo != null && mBackNavigationInfo.isAnimationCallback()) {

            final BackMotionEvent backMotionEvent = mTouchTracker.createProgressEvent();
            if (backMotionEvent != null) {
                // Constraints - absolute values
                float minVelocity = mFlingAnimationUtils.getMinVelocityPxPerSecond();
                float maxVelocity = mFlingAnimationUtils.getHighVelocityPxPerSecond();
                float maxX = mTouchTracker.getMaxX(); // px
                float maxFlingDistance = maxX * MAX_FLING_PROGRESS; // px

                // Current state
                float currentX = backMotionEvent.getTouchX();
                float velocity = MathUtils.constrain(backMotionEvent.getVelocityX(),
                        -maxVelocity, maxVelocity);

                // Target state
                float animationFaction = velocity / maxVelocity; // value between -1 and 1
                float flingDistance = animationFaction * maxFlingDistance; // px
                float endX = MathUtils.constrain(currentX + flingDistance, 0f, maxX);

                if (!Float.isNaN(endX)
                        && currentX != endX
                        && Math.abs(velocity) >= minVelocity) {
                    ValueAnimator animator = ValueAnimator.ofFloat(currentX, endX);

                    mFlingAnimationUtils.apply(
                            /* animator = */ animator,
                            /* currValue = */ currentX,
                            /* endValue = */ endX,
                            /* velocity = */ velocity,
                            /* maxDistance = */ maxFlingDistance
                    );

                    animator.addUpdateListener(animation -> {
                        Float animatedValue = (Float) animation.getAnimatedValue();
                        float progress = mTouchTracker.getProgress(animatedValue);
                        final BackMotionEvent backEvent = mTouchTracker
                                .createProgressEvent(progress);
                        dispatchOnBackProgressed(mActiveCallback, backEvent);
                    });

                    animator.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            dispatchOnBackInvoked(callback);
                        }
                    });
                    animator.start();
                    animationStarted = true;
                }
            }
        }

        if (!animationStarted) {
            dispatchOnBackInvoked(callback);
        }
    }

    private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
        if (callback == null) {
            return;
@@ -530,7 +625,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
        if (mBackNavigationInfo != null) {
            final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
            if (mTriggerBack) {
                dispatchOnBackInvoked(callback);
                dispatchOrAnimateOnBackInvoked(callback);
            } else {
                dispatchOnBackCancelled(callback);
            }
@@ -605,7 +700,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont

        // The next callback should be {@link #onBackAnimationFinished}.
        if (mTriggerBack) {
            dispatchOnBackInvoked(mActiveCallback);
            dispatchOrAnimateOnBackInvoked(mActiveCallback);
        } else {
            dispatchOnBackCancelled(mActiveCallback);
        }
+32 −15
Original line number Diff line number Diff line
@@ -16,7 +16,10 @@

package com.android.wm.shell.back;

import android.annotation.FloatRange;
import android.os.SystemProperties;
import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.window.BackEvent;
import android.window.BackMotionEvent;
@@ -99,26 +102,40 @@ class TouchTracker {
    }

    BackMotionEvent createProgressEvent() {
        float progressThreshold = PROGRESS_THRESHOLD >= 0
                ? PROGRESS_THRESHOLD : mProgressThreshold;
        progressThreshold = progressThreshold == 0 ? 1 : progressThreshold;
        float progress = 0;
        // Progress is always 0 when back is cancelled and not restarted.
        if (!mCancelled) {
            progress = getProgress(mLatestTouchX);
        }
        return createProgressEvent(progress);
    }

    /**
     * Progress value computed from the touch position.
     *
     * @param touchX the X touch position of the {@link MotionEvent}.
     * @return progress value
     */
    @FloatRange(from = 0.0, to = 1.0)
    float getProgress(float touchX) {
        // If back is committed, progress is the distance between the last and first touch
        // point, divided by the max drag distance. Otherwise, it's the distance between
        // the last touch point and the starting threshold, divided by max drag distance.
        // The starting threshold is initially the first touch location, and updated to
        // the location everytime back is restarted after being cancelled.
        float startX = mTriggerBack ? mInitTouchX : mStartThresholdX;
            float deltaX = Math.max(
                    mSwipeEdge == BackEvent.EDGE_LEFT
                            ? mLatestTouchX - startX
                            : startX - mLatestTouchX,
                    0);
            progress = Math.min(Math.max(deltaX / progressThreshold, 0), 1);
        float deltaX = Math.abs(startX - touchX);
        float maxX = getMaxX();
        maxX = maxX == 0 ? 1 : maxX;
        return MathUtils.constrain(deltaX / maxX, 0, 1);
    }
        return createProgressEvent(progress);

    /**
     * Maximum X value (in pixels).
     * Progress is considered to be completed (1f) when this limit is exceeded.
     */
    float getMaxX() {
        return PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold;
    }

    BackMotionEvent createProgressEvent(float progress) {
+55 −10
Original line number Diff line number Diff line
@@ -135,12 +135,15 @@ public class BackAnimationControllerTest extends ShellTestCase {
        mShellExecutor.flushAll();
    }

    private void createNavigationInfo(int backType, boolean enableAnimation) {
    private void createNavigationInfo(int backType,
            boolean enableAnimation,
            boolean isAnimationCallback) {
        BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
                .setType(backType)
                .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
                .setOnBackInvokedCallback(mAppCallback)
                .setPrepareRemoteAnimation(enableAnimation);
                .setPrepareRemoteAnimation(enableAnimation)
                .setAnimationCallback(isAnimationCallback);

        createNavigationInfo(builder);
    }
@@ -218,7 +221,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
    @Test
    public void backToHome_dispatchesEvents() throws RemoteException {
        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
                /* enableAnimation = */ true,
                /* isAnimationCallback = */ false);

        doMotionEvent(MotionEvent.ACTION_DOWN, 0);

@@ -239,6 +244,32 @@ public class BackAnimationControllerTest extends ShellTestCase {
        verify(mAnimatorCallback).onBackInvoked();
    }

    @Test
    public void backToHomeWithAnimationCallback_dispatchesEvents() throws RemoteException {
        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
                /* enableAnimation = */ true,
                /* isAnimationCallback = */ true);

        doMotionEvent(MotionEvent.ACTION_DOWN, 0);

        // Check that back start and progress is dispatched when first move.
        doMotionEvent(MotionEvent.ACTION_MOVE, 100, 3000);

        simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);

        verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class));
        verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
        ArgumentCaptor<BackMotionEvent> backEventCaptor =
                ArgumentCaptor.forClass(BackMotionEvent.class);
        verify(mAnimatorCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());

        // Check that back invocation is dispatched.
        mController.setTriggerBack(true);   // Fake trigger back
        doMotionEvent(MotionEvent.ACTION_UP, 0);
        verify(mAnimatorCallback).onBackInvoked();
    }

    @Test
    public void animationDisabledFromSettings() throws RemoteException {
        // Toggle the setting off
@@ -254,7 +285,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
        ArgumentCaptor<BackMotionEvent> backEventCaptor =
                ArgumentCaptor.forClass(BackMotionEvent.class);

        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false);
        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
                /* enableAnimation = */ false,
                /* isAnimationCallback = */ false);

        triggerBackGesture();
        releaseBackGesture();
@@ -271,7 +304,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
    @Test
    public void ignoresGesture_transitionInProgress() throws RemoteException {
        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
                /* enableAnimation = */ true,
                /* isAnimationCallback = */ false);

        triggerBackGesture();
        simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
@@ -309,7 +344,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
    @Test
    public void acceptsGesture_transitionTimeout() throws RemoteException {
        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
                /* enableAnimation = */ true,
                /* isAnimationCallback = */ false);

        // In case it is still running in animation.
        doNothing().when(mAnimatorCallback).onBackInvoked();
@@ -334,7 +371,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
    public void cancelBackInvokeWhenLostFocus() throws RemoteException {
        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);

        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
                /* enableAnimation = */ true,
                /* isAnimationCallback = */ false);

        doMotionEvent(MotionEvent.ACTION_DOWN, 0);
        // Check that back start and progress is dispatched when first move.
@@ -454,7 +493,9 @@ public class BackAnimationControllerTest extends ShellTestCase {

        mController.registerAnimation(type, animationRunner);

        createNavigationInfo(type, true);
        createNavigationInfo(type,
                /* enableAnimation = */ true,
                /* isAnimationCallback = */ false);

        doMotionEvent(MotionEvent.ACTION_DOWN, 0);

@@ -473,11 +514,15 @@ public class BackAnimationControllerTest extends ShellTestCase {
    }

    private void doMotionEvent(int actionDown, int coordinate) {
        doMotionEvent(actionDown, coordinate, 0);
    }

    private void doMotionEvent(int actionDown, int coordinate, float velocity) {
        mController.onMotionEvent(
                /* touchX */ coordinate,
                /* touchY */ coordinate,
                /* velocityX = */ 0,
                /* velocityY = */ 0,
                /* velocityX = */ velocity,
                /* velocityY = */ velocity,
                /* keyAction */ actionDown,
                /* swipeEdge */ BackEvent.EDGE_LEFT);
    }