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

Commit 77bf622c authored by Tony Wickham's avatar Tony Wickham Committed by Android (Google) Code Review
Browse files

Merge "Make fling thresholds consistent" into ub-launcher3-edmonton

parents 3c25046d 0f640b6c
Loading
Loading
Loading
Loading
+18 −21
Original line number Original line Diff line number Diff line
@@ -21,16 +21,17 @@ import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelo
import android.animation.Animator;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator;
import android.util.Log;
import android.view.MotionEvent;
import android.view.MotionEvent;


import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.Utilities;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.FlingBlockCheck;
import com.android.launcher3.util.PendingAnimation;
import com.android.launcher3.util.PendingAnimation;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.views.BaseDragLayer;
@@ -46,8 +47,6 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>


    private static final String TAG = "OverviewSwipeController";
    private static final String TAG = "OverviewSwipeController";


    private static final float ALLOWED_FLING_DIRECTION_CHANGE_PROGRESS = 0.1f;

    // Progress after which the transition is assumed to be a success in case user does not fling
    // Progress after which the transition is assumed to be a success in case user does not fling
    private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
    private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;


@@ -65,6 +64,7 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
    private float mDisplacementShift;
    private float mDisplacementShift;
    private float mProgressMultiplier;
    private float mProgressMultiplier;
    private float mEndDisplacement;
    private float mEndDisplacement;
    private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();


    private TaskView mTaskBeingDragged;
    private TaskView mTaskBeingDragged;


@@ -93,7 +93,6 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
    @Override
    @Override
    public void onAnimationCancel(Animator animation) {
    public void onAnimationCancel(Animator animation) {
        if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
        if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
            Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
            clearState();
            clearState();
        }
        }
    }
    }
@@ -158,16 +157,16 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
        return mDetector.onTouchEvent(ev);
        return mDetector.onTouchEvent(ev);
    }
    }


    private boolean reInitAnimationController(boolean goingUp) {
    private void reInitAnimationController(boolean goingUp) {
        if (mCurrentAnimation != null && mCurrentAnimationIsGoingUp == goingUp) {
        if (mCurrentAnimation != null && mCurrentAnimationIsGoingUp == goingUp) {
            // No need to init
            // No need to init
            return false;
            return;
        }
        }
        int scrollDirections = mDetector.getScrollDirections();
        int scrollDirections = mDetector.getScrollDirections();
        if (goingUp && ((scrollDirections & SwipeDetector.DIRECTION_POSITIVE) == 0)
        if (goingUp && ((scrollDirections & SwipeDetector.DIRECTION_POSITIVE) == 0)
                || !goingUp && ((scrollDirections & SwipeDetector.DIRECTION_NEGATIVE) == 0)) {
                || !goingUp && ((scrollDirections & SwipeDetector.DIRECTION_NEGATIVE) == 0)) {
            // Trying to re-init in an unsupported direction.
            // Trying to re-init in an unsupported direction.
            return false;
            return;
        }
        }
        if (mCurrentAnimation != null) {
        if (mCurrentAnimation != null) {
            mCurrentAnimation.setPlayFraction(0);
            mCurrentAnimation.setPlayFraction(0);
@@ -205,7 +204,6 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
        mCurrentAnimation.getTarget().addListener(this);
        mCurrentAnimation.getTarget().addListener(this);
        mCurrentAnimation.dispatchOnStart();
        mCurrentAnimation.dispatchOnStart();
        mProgressMultiplier = 1 / mEndDisplacement;
        mProgressMultiplier = 1 / mEndDisplacement;
        return true;
    }
    }


    @Override
    @Override
@@ -217,6 +215,7 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
            mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier;
            mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier;
            mCurrentAnimation.pause();
            mCurrentAnimation.pause();
        }
        }
        mFlingBlockCheck.unblockFling();
    }
    }


    @Override
    @Override
@@ -226,6 +225,9 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
                totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0;
                totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0;
        if (isGoingUp != mCurrentAnimationIsGoingUp) {
        if (isGoingUp != mCurrentAnimationIsGoingUp) {
            reInitAnimationController(isGoingUp);
            reInitAnimationController(isGoingUp);
            mFlingBlockCheck.blockFling();
        } else {
            mFlingBlockCheck.onEvent();
        }
        }
        mCurrentAnimation.setPlayFraction(totalDisplacement * mProgressMultiplier);
        mCurrentAnimation.setPlayFraction(totalDisplacement * mProgressMultiplier);
        return true;
        return true;
@@ -235,22 +237,14 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
    public void onDragEnd(float velocity, boolean fling) {
    public void onDragEnd(float velocity, boolean fling) {
        final boolean goingToEnd;
        final boolean goingToEnd;
        final int logAction;
        final int logAction;
        boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
        if (blockedFling) {
            fling = false;
        }
        if (fling) {
        if (fling) {
            logAction = Touch.FLING;
            logAction = Touch.FLING;
            boolean goingUp = velocity < 0;
            boolean goingUp = velocity < 0;
            if (goingUp != mCurrentAnimationIsGoingUp) {
            goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
                // In case the fling is in opposite direction, make sure if is close enough
                // from the start position
                if (mCurrentAnimation.getProgressFraction()
                        >= ALLOWED_FLING_DIRECTION_CHANGE_PROGRESS) {
                    // Not allowed
                    goingToEnd = false;
                } else {
                    goingToEnd = reInitAnimationController(goingUp);
                }
            } else {
                goingToEnd = true;
            }
        } else {
        } else {
            logAction = Touch.SWIPE;
            logAction = Touch.SWIPE;
            goingToEnd = mCurrentAnimation.getProgressFraction() > SUCCESS_TRANSITION_PROGRESS;
            goingToEnd = mCurrentAnimation.getProgressFraction() > SUCCESS_TRANSITION_PROGRESS;
@@ -259,6 +253,9 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
        float progress = mCurrentAnimation.getProgressFraction();
        float progress = mCurrentAnimation.getProgressFraction();
        long animationDuration = SwipeDetector.calculateDuration(
        long animationDuration = SwipeDetector.calculateDuration(
                velocity, goingToEnd ? (1 - progress) : progress);
                velocity, goingToEnd ? (1 - progress) : progress);
        if (blockedFling && !goingToEnd) {
            animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity);
        }


        float nextFrameProgress = Utilities.boundToRange(
        float nextFrameProgress = Utilities.boundToRange(
                progress + velocity * SINGLE_FRAME_MS / Math.abs(mEndDisplacement), 0f, 1f);
                progress + velocity * SINGLE_FRAME_MS / Math.abs(mEndDisplacement), 0f, 1f);
+18 −2
Original line number Original line Diff line number Diff line
@@ -15,6 +15,7 @@
 */
 */
package com.android.quickstep;
package com.android.quickstep;


import static com.android.launcher3.LauncherAnimUtils.MIN_PROGRESS_TO_ALL_APPS;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
@@ -26,6 +27,7 @@ import android.animation.ValueAnimator;
import android.view.Surface;
import android.view.Surface;


import com.android.launcher3.Launcher;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.allapps.DiscoveryBounce;
@@ -33,7 +35,9 @@ import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.FlingBlockCheck;
import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.TransactionCompat;
import com.android.systemui.shared.system.TransactionCompat;


@@ -44,7 +48,6 @@ import com.android.systemui.shared.system.TransactionCompat;
 */
 */
public class LongSwipeHelper {
public class LongSwipeHelper {


    private static final float MIN_PROGRESS_TO_ALL_APPS = 0.35f;
    private static final float SWIPE_DURATION_MULTIPLIER =
    private static final float SWIPE_DURATION_MULTIPLIER =
            Math.min(1 / MIN_PROGRESS_TO_ALL_APPS, 1 / (1 - MIN_PROGRESS_TO_ALL_APPS));
            Math.min(1 / MIN_PROGRESS_TO_ALL_APPS, 1 / (1 - MIN_PROGRESS_TO_ALL_APPS));


@@ -53,6 +56,7 @@ public class LongSwipeHelper {


    private float mMaxSwipeDistance = 1;
    private float mMaxSwipeDistance = 1;
    private AnimatorPlaybackController mAnimator;
    private AnimatorPlaybackController mAnimator;
    private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();


    LongSwipeHelper(Launcher launcher, RemoteAnimationTargetSet targetSet) {
    LongSwipeHelper(Launcher launcher, RemoteAnimationTargetSet targetSet) {
        mLauncher = launcher;
        mLauncher = launcher;
@@ -62,6 +66,7 @@ public class LongSwipeHelper {


    private void init() {
    private void init() {
        setTargetAlpha(0, true);
        setTargetAlpha(0, true);
        mFlingBlockCheck.blockFling();


        // Init animations
        // Init animations
        AllAppsTransitionController controller = mLauncher.getAllAppsController();
        AllAppsTransitionController controller = mLauncher.getAllAppsController();
@@ -74,6 +79,7 @@ public class LongSwipeHelper {


    public void onMove(float displacement) {
    public void onMove(float displacement) {
        mAnimator.setPlayFraction(displacement / mMaxSwipeDistance);
        mAnimator.setPlayFraction(displacement / mMaxSwipeDistance);
        mFlingBlockCheck.onEvent();
    }
    }


    public void destroy() {
    public void destroy() {
@@ -90,6 +96,11 @@ public class LongSwipeHelper {
        final boolean toAllApps;
        final boolean toAllApps;
        float endProgress;
        float endProgress;


        boolean blockedFling = isFling && mFlingBlockCheck.isBlocked();
        if (blockedFling) {
            isFling = false;
        }

        if (!isFling) {
        if (!isFling) {
            toAllApps = currentFraction > MIN_PROGRESS_TO_ALL_APPS;
            toAllApps = currentFraction > MIN_PROGRESS_TO_ALL_APPS;
            endProgress = toAllApps ? 1 : 0;
            endProgress = toAllApps ? 1 : 0;
@@ -114,7 +125,11 @@ public class LongSwipeHelper {
            }
            }
        }
        }


        mAnimator.setEndAction(() -> onSwipeAnimationComplete(toAllApps, isFling, callback));
        if (blockedFling && !toAllApps) {
            duration *= LauncherAnimUtils.blockedFlingDurationFactor(0);
        }
        final boolean finalIsFling = isFling;
        mAnimator.setEndAction(() -> onSwipeAnimationComplete(toAllApps, finalIsFling, callback));
        ValueAnimator animator = mAnimator.getAnimationPlayer();
        ValueAnimator animator = mAnimator.getAnimationPlayer();
        animator.setDuration(duration).setInterpolator(DEACCEL);
        animator.setDuration(duration).setInterpolator(DEACCEL);
        animator.setFloatValues(currentFraction, endProgress);
        animator.setFloatValues(currentFraction, endProgress);
@@ -150,6 +165,7 @@ public class LongSwipeHelper {
        mLauncher.getStateManager().goToState(toAllApps ? ALL_APPS : OVERVIEW, false);
        mLauncher.getStateManager().goToState(toAllApps ? ALL_APPS : OVERVIEW, false);
        if (!toAllApps) {
        if (!toAllApps) {
            DiscoveryBounce.showForOverviewIfNeeded(mLauncher);
            DiscoveryBounce.showForOverviewIfNeeded(mLauncher);
            mLauncher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(true);
        }
        }


        mLauncher.getUserEventDispatcher().logStateChangeAction(
        mLauncher.getUserEventDispatcher().logStateChangeAction(
+7 −12
Original line number Original line Diff line number Diff line
@@ -39,6 +39,9 @@ public class LauncherAnimUtils {
    public static final int SPRING_LOADED_TRANSITION_MS = 150;
    public static final int SPRING_LOADED_TRANSITION_MS = 150;
    public static final int SPRING_LOADED_EXIT_DELAY = 500;
    public static final int SPRING_LOADED_EXIT_DELAY = 500;


    // The progress of an animation to all apps must be at least this far along to snap to all apps.
    public static final float MIN_PROGRESS_TO_ALL_APPS = 0.5f;

    static WeakHashMap<Animator, Object> sAnimators = new WeakHashMap<Animator, Object>();
    static WeakHashMap<Animator, Object> sAnimators = new WeakHashMap<Animator, Object>();
    static Animator.AnimatorListener sEndAnimListener = new Animator.AnimatorListener() {
    static Animator.AnimatorListener sEndAnimListener = new Animator.AnimatorListener() {
        public void onAnimationStart(Animator animation) {
        public void onAnimationStart(Animator animation) {
@@ -165,16 +168,8 @@ public class LauncherAnimUtils {
                }
                }
            };
            };


    public static final Property<View, Float> ELEVATION =
    /** Increase the duration if we prevented the fling, as we are going against a high velocity. */
            new Property<View, Float>(Float.class, "elevation") {
    public static int blockedFlingDurationFactor(float velocity) {
                @Override
        return (int) Utilities.boundToRange(Math.abs(velocity) / 2, 2f, 6f);
                public Float get(View view) {
                    return view.getElevation();
    }
    }

                @Override
                public void set(View view, Float elevation) {
                    view.setElevation(elevation);
                }
            };
}
}
+19 −10
Original line number Original line Diff line number Diff line
@@ -15,6 +15,7 @@
 */
 */
package com.android.launcher3.touch;
package com.android.launcher3.touch;


import static com.android.launcher3.LauncherAnimUtils.MIN_PROGRESS_TO_ALL_APPS;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -32,6 +33,7 @@ import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.MotionEvent;


import com.android.launcher3.Launcher;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationComponents;
import com.android.launcher3.LauncherStateManager.AnimationComponents;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
@@ -43,6 +45,7 @@ import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.FlingBlockCheck;
import com.android.launcher3.util.PendingAnimation;
import com.android.launcher3.util.PendingAnimation;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.TouchController;


@@ -80,7 +83,7 @@ public abstract class AbstractStateChangeTouchController
    private float mProgressMultiplier;
    private float mProgressMultiplier;
    private float mDisplacementShift;
    private float mDisplacementShift;
    private boolean mCanBlockFling;
    private boolean mCanBlockFling;
    private boolean mBlockFling;
    private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();


    private AnimatorSet mAtomicAnim;
    private AnimatorSet mAtomicAnim;
    private boolean mPassedOverviewAtomicThreshold;
    private boolean mPassedOverviewAtomicThreshold;
@@ -241,7 +244,7 @@ public abstract class AbstractStateChangeTouchController
            mStartProgress = mCurrentAnimation.getProgressFraction();
            mStartProgress = mCurrentAnimation.getProgressFraction();
        }
        }
        mCanBlockFling = mFromState == NORMAL;
        mCanBlockFling = mFromState == NORMAL;
        mBlockFling = false;
        mFlingBlockCheck.unblockFling();
    }
    }


    @Override
    @Override
@@ -253,16 +256,19 @@ public abstract class AbstractStateChangeTouchController
        if (progress <= 0) {
        if (progress <= 0) {
            if (reinitCurrentAnimation(false, isDragTowardPositive)) {
            if (reinitCurrentAnimation(false, isDragTowardPositive)) {
                mDisplacementShift = displacement;
                mDisplacementShift = displacement;
                mBlockFling = mCanBlockFling;
                if (mCanBlockFling) {
                    mFlingBlockCheck.blockFling();
                }
            }
            }
        } else if (progress >= 1) {
        } else if (progress >= 1) {
            if (reinitCurrentAnimation(true, isDragTowardPositive)) {
            if (reinitCurrentAnimation(true, isDragTowardPositive)) {
                mDisplacementShift = displacement;
                mDisplacementShift = displacement;
                mBlockFling = mCanBlockFling;
                if (mCanBlockFling) {
                    mFlingBlockCheck.blockFling();
                }
            }
            }
        } else if (Math.abs(velocity) < SwipeDetector.RELEASE_VELOCITY_PX_MS) {
        } else {
            // We prevent flinging after passing a state, but allow it if the user pauses briefly.
            mFlingBlockCheck.onEvent();
            mBlockFling = false;
        }
        }


        return true;
        return true;
@@ -325,7 +331,7 @@ public abstract class AbstractStateChangeTouchController
        final LauncherState targetState;
        final LauncherState targetState;
        final float progress = mCurrentAnimation.getProgressFraction();
        final float progress = mCurrentAnimation.getProgressFraction();


        boolean blockedFling = fling && mBlockFling;
        boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
        if (blockedFling) {
        if (blockedFling) {
            fling = false;
            fling = false;
        }
        }
@@ -338,14 +344,17 @@ public abstract class AbstractStateChangeTouchController
            // snap to top or bottom using the release velocity
            // snap to top or bottom using the release velocity
        } else {
        } else {
            logAction = Touch.SWIPE;
            logAction = Touch.SWIPE;
            targetState = (progress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
            float successProgress = mToState == ALL_APPS
                    ? MIN_PROGRESS_TO_ALL_APPS : SUCCESS_TRANSITION_PROGRESS;
            targetState = (progress > successProgress) ? mToState : mFromState;
        }
        }


        final float endProgress;
        final float endProgress;
        final float startProgress;
        final float startProgress;
        final long duration;
        final long duration;
        // Increase the duration if we prevented the fling, as we are going against a high velocity.
        // Increase the duration if we prevented the fling, as we are going against a high velocity.
        final long durationMultiplier = blockedFling && targetState == mFromState ? 6 : 1;
        final int durationMultiplier = blockedFling && targetState == mFromState
                ? LauncherAnimUtils.blockedFlingDurationFactor(velocity) : 1;


        if (targetState == mToState) {
        if (targetState == mToState) {
            endProgress = 1;
            endProgress = 1;
+52 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.launcher3.util;

import android.os.SystemClock;

/**
 * Determines whether a fling should be blocked. Currently we block flings when crossing thresholds
 * to new states, and unblock after a short duration.
 */
public class FlingBlockCheck {
    // Allow flinging to a new state after waiting this many milliseconds.
    private static final long UNBLOCK_FLING_PAUSE_DURATION = 200;

    private boolean mBlockFling;
    private long mBlockFlingTime;

    public void blockFling() {
        mBlockFling = true;
        mBlockFlingTime = SystemClock.uptimeMillis();
    }

    public void unblockFling() {
        mBlockFling = false;
        mBlockFlingTime = 0;
    }

    public void onEvent() {
        // We prevent flinging after passing a state, but allow it if the user pauses briefly.
        if (SystemClock.uptimeMillis() - mBlockFlingTime >= UNBLOCK_FLING_PAUSE_DURATION) {
            mBlockFling = false;
        }
    }

    public boolean isBlocked() {
        return mBlockFling;
    }
}