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

Commit 89f8d9ca authored by Omar Miatello's avatar Omar Miatello Committed by Android (Google) Code Review
Browse files

Merge "BackEvent: add generated events for fling gesture" into udc-dev

parents bfd1a287 caec34ff
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);
    }