Merge "Log time spent on different UI surfaces" into ub-launcher3-master
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index eda5bb1..46eb263 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1033,7 +1033,7 @@
             return;
         }
 
-        int stateOrdinal = savedState.getInt(RUNTIME_STATE, LauncherState.NORMAL.ordinal());
+        int stateOrdinal = savedState.getInt(RUNTIME_STATE, LauncherState.NORMAL.ordinal);
         LauncherState[] stateValues = LauncherState.values();
         LauncherState state = stateValues[stateOrdinal];
         if (!state.doNotRestore) {
@@ -1502,7 +1502,7 @@
             outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getNextPage());
 
         }
-        outState.putInt(RUNTIME_STATE, mWorkspace.getState().ordinal());
+        outState.putInt(RUNTIME_STATE, mWorkspace.getState().ordinal);
 
 
         AbstractFloatingView widgets = AbstractFloatingView
@@ -2344,7 +2344,7 @@
     public boolean onLongClick(View v) {
         if (!isDraggingEnabled()) return false;
         if (isWorkspaceLocked()) return false;
-        if (!isInState(LauncherState.NORMAL)) return false;
+        if (!isInState(LauncherState.NORMAL) && !isInState(LauncherState.OVERVIEW)) return false;
 
         boolean ignoreLongPressToOverview =
                 mDeviceProfile.shouldIgnoreLongPressToOverview(mLastDispatchTouchEventX);
@@ -2539,7 +2539,7 @@
 
     public void enterSpringLoadedDragMode() {
         if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s",
-                mWorkspace.getState().name()));
+                mWorkspace.getState().ordinal));
         if (isInState(LauncherState.SPRING_LOADED)) {
             return;
         }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index c51b920..4619f4e 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -21,34 +21,41 @@
 import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
-import static com.android.launcher3.StateFlags.FLAG_DISABLE_ACCESSIBILITY;
-import static com.android.launcher3.StateFlags.FLAG_DO_NOT_RESTORE;
-import static com.android.launcher3.StateFlags.FLAG_HIDE_HOTSEAT;
-import static com.android.launcher3.StateFlags.FLAG_MULTI_PAGE;
-import static com.android.launcher3.StateFlags.FLAG_SHOW_SCRIM;
 
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
-interface StateFlags {
-    int FLAG_SHOW_SCRIM = 1 << 0;
-    int FLAG_MULTI_PAGE = 1 << 1;
-    int FLAG_HIDE_HOTSEAT = 1 << 2;
-    int FLAG_DISABLE_ACCESSIBILITY = 1 << 3;
-    int FLAG_DO_NOT_RESTORE = 1 << 4;
-}
+import java.util.Arrays;
+
 
 /**
  * Various states for launcher
  */
-public enum LauncherState {
+public class LauncherState {
 
-    NORMAL          (ContainerType.WORKSPACE, 0, FLAG_DO_NOT_RESTORE),
-    ALL_APPS        (ContainerType.ALLAPPS, ALL_APPS_TRANSITION_MS, FLAG_DISABLE_ACCESSIBILITY),
-    SPRING_LOADED   (ContainerType.WORKSPACE, SPRING_LOADED_TRANSITION_MS,
-            FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE | FLAG_DISABLE_ACCESSIBILITY | FLAG_DO_NOT_RESTORE),
-    OVERVIEW        (ContainerType.OVERVIEW, OVERVIEW_TRANSITION_MS,
+    protected static final int FLAG_SHOW_SCRIM = 1 << 0;
+    protected static final int FLAG_MULTI_PAGE = 1 << 1;
+    protected static final int FLAG_HIDE_HOTSEAT = 1 << 2;
+    protected static final int FLAG_DISABLE_ACCESSIBILITY = 1 << 3;
+    protected static final int FLAG_DO_NOT_RESTORE = 1 << 4;
+
+    private static final LauncherState[] sAllStates = new LauncherState[4];
+
+    public static LauncherState NORMAL = new LauncherState(0, ContainerType.WORKSPACE,
+            0, FLAG_DO_NOT_RESTORE);
+
+    public static LauncherState ALL_APPS = new LauncherState(1, ContainerType.ALLAPPS,
+            ALL_APPS_TRANSITION_MS, FLAG_DISABLE_ACCESSIBILITY);
+
+    public static LauncherState SPRING_LOADED = new LauncherState(2, ContainerType.WORKSPACE,
+            SPRING_LOADED_TRANSITION_MS,
+            FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE | FLAG_DISABLE_ACCESSIBILITY | FLAG_DO_NOT_RESTORE);
+
+    public static LauncherState OVERVIEW = new LauncherState(3, ContainerType.OVERVIEW,
+            OVERVIEW_TRANSITION_MS,
             FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE | FLAG_HIDE_HOTSEAT | FLAG_DO_NOT_RESTORE);
 
+    public final int ordinal;
+
     /**
      * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
      */
@@ -75,7 +82,7 @@
     public final boolean hideHotseat;
     public final int transitionDuration;
 
-    LauncherState(int containerType, int transitionDuration, int flags) {
+    public LauncherState(int id, int containerType, int transitionDuration, int flags) {
         this.containerType = containerType;
         this.transitionDuration = transitionDuration;
 
@@ -86,5 +93,12 @@
                 ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
                 : IMPORTANT_FOR_ACCESSIBILITY_AUTO;
         this.doNotRestore = (flags & FLAG_DO_NOT_RESTORE) != 0;
+
+        this.ordinal = id;
+        sAllStates[id] = this;
+    }
+
+    public static LauncherState[] values() {
+        return Arrays.copyOf(sAllStates, sAllStates.length);
     }
 }
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 40d5495..0e6cead 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -234,10 +234,8 @@
     private void startAnimationToNewWorkspaceState(
             final LauncherState toWorkspaceState, final boolean animated,
             final Runnable onCompleteRunnable) {
-        final View fromWorkspace = mLauncher.getWorkspace();
         // Cancel the current animation
         cancelAnimation();
-
         mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
 
         if (!animated) {
@@ -249,11 +247,20 @@
             return;
         }
 
+        final AnimatorSet animation =
+                createAnimationToNewWorkspace(toWorkspaceState, onCompleteRunnable);
+        mLauncher.getWorkspace().post(new StartAnimRunnable(animation, null));
+        mCurrentAnimation = animation;
+    }
+
+    protected AnimatorSet createAnimationToNewWorkspace(LauncherState state,
+            final Runnable onCompleteRunnable) {
+        cancelAnimation();
+
         final AnimationLayerSet layerViews = new AnimationLayerSet();
         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
         mConfig.reset();
-        mLauncher.getWorkspace().setStateWithAnimation(toWorkspaceState,
-                layerViews, animation, mConfig);
+        mLauncher.getWorkspace().setStateWithAnimation(state, layerViews, animation, mConfig);
 
         animation.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -268,8 +275,7 @@
             }
         });
         animation.addListener(layerViews);
-        fromWorkspace.post(new StartAnimRunnable(animation, null));
-        mCurrentAnimation = animation;
+        return animation;
     }
 
     /**
diff --git a/src/com/android/launcher3/PinchAnimationManager.java b/src/com/android/launcher3/PinchAnimationManager.java
deleted file mode 100644
index bbe8e89..0000000
--- a/src/com/android/launcher3/PinchAnimationManager.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.util.Log;
-import android.view.View;
-import android.view.animation.LinearInterpolator;
-
-import com.android.launcher3.anim.AnimationLayerSet;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-/**
- * Manages the animations that play as the user pinches to/from overview mode.
- *
- *  It will look like this pinching in:
- * - Workspace scales down
- * - At some threshold 1, hotseat and QSB fade out (full animation)
- * - At a later threshold 2, panel buttons fade in and scrim fades in
- * - At a final threshold 3, snap to overview
- *
- * Pinching out:
- * - Workspace scales up
- * - At threshold 1, panel buttons fade out
- * - At threshold 2, hotseat and QSB fade in and scrim fades out
- * - At threshold 3, snap to workspace
- *
- * @see PinchToOverviewListener
- * @see PinchThresholdManager
- */
-public class PinchAnimationManager {
-    private static final String TAG = "PinchAnimationManager";
-
-    private static final int THRESHOLD_ANIM_DURATION = 150;
-    private static final LinearInterpolator INTERPOLATOR = new LinearInterpolator();
-
-    private static final int INDEX_HOTSEAT = 0;
-    private static final int INDEX_OVERVIEW_PANEL_BUTTONS = 1;
-    private static final int INDEX_SCRIM = 2;
-
-    private final Animator[] mAnimators = new Animator[3];
-
-    private Launcher mLauncher;
-    private Workspace mWorkspace;
-
-    private float mOverviewScale;
-    private float mOverviewTranslationY;
-    private int mNormalOverviewTransitionDuration;
-    private boolean mIsAnimating;
-
-    public PinchAnimationManager(Launcher launcher) {
-        mLauncher = launcher;
-        mWorkspace = launcher.mWorkspace;
-
-        mOverviewScale = mWorkspace.getOverviewModeShrinkFactor();
-        mOverviewTranslationY = mWorkspace.getOverviewModeTranslationY();
-        mNormalOverviewTransitionDuration = LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-    }
-
-    public int getNormalOverviewTransitionDuration() {
-        return mNormalOverviewTransitionDuration;
-    }
-
-    /**
-     * Interpolate from {@param currentProgress} to {@param toProgress}, calling
-     * {@link #setAnimationProgress(float)} throughout the duration. If duration is -1,
-     * the default overview transition duration is used.
-     */
-    public void animateToProgress(float currentProgress, float toProgress, int duration,
-            final PinchThresholdManager thresholdManager) {
-        if (duration == -1) {
-            duration = mNormalOverviewTransitionDuration;
-        }
-        ValueAnimator animator = ValueAnimator.ofFloat(currentProgress, toProgress);
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    float pinchProgress = (Float) animation.getAnimatedValue();
-                    setAnimationProgress(pinchProgress);
-                    thresholdManager.updateAndAnimatePassedThreshold(pinchProgress,
-                            PinchAnimationManager.this);
-                }
-            }
-        );
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mIsAnimating = false;
-                thresholdManager.reset();
-                mWorkspace.onEndStateTransition();
-            }
-        });
-        animator.setDuration(duration).start();
-        mIsAnimating = true;
-    }
-
-    public boolean isAnimating() {
-        return mIsAnimating;
-    }
-
-    /**
-     * Animates to the specified progress. This should be called repeatedly throughout the pinch
-     * gesture to run animations that interpolate throughout the gesture.
-     * @param interpolatedProgress The progress from 0 to 1, where 0 is overview and 1 is workspace.
-     */
-    public void setAnimationProgress(float interpolatedProgress) {
-        float interpolatedScale = interpolatedProgress * (1f - mOverviewScale) + mOverviewScale;
-        float interpolatedTranslationY = (1f - interpolatedProgress) * mOverviewTranslationY;
-        mWorkspace.setScaleX(interpolatedScale);
-        mWorkspace.setScaleY(interpolatedScale);
-        mWorkspace.setTranslationY(interpolatedTranslationY);
-        int alpha = (int) ((1f - interpolatedProgress) * 255);
-        setOverviewPanelsAlpha(alpha, 0);
-    }
-
-    /**
-     * Animates certain properties based on which threshold was passed, and in what direction. The
-     * starting state must also be taken into account because the thresholds mean different things
-     * when going from workspace to overview and vice versa.
-     * @param threshold One of {@link PinchThresholdManager#THRESHOLD_ONE},
-     *                  {@link PinchThresholdManager#THRESHOLD_TWO}, or
-     *                  {@link PinchThresholdManager#THRESHOLD_THREE}
-     * @param startState {@link LauncherState#NORMAL} or {@link LauncherState#OVERVIEW}.
-     * @param goingTowards {@link LauncherState#NORMAL} or {@link LauncherState#OVERVIEW}.
-     *                     Note that this doesn't have to be the opposite of startState;
-     */
-    public void animateThreshold(float threshold, LauncherState startState,
-            LauncherState goingTowards) {
-        if (threshold == PinchThresholdManager.THRESHOLD_ONE) {
-            if (startState == OVERVIEW) {
-                animateOverviewPanelButtons(goingTowards == OVERVIEW);
-            } else if (startState == NORMAL) {
-                animateHotseatAndQsb(goingTowards == NORMAL);
-            }
-        } else if (threshold == PinchThresholdManager.THRESHOLD_TWO) {
-            if (startState == OVERVIEW) {
-                animateHotseatAndQsb(goingTowards == NORMAL);
-                animateScrim(goingTowards == OVERVIEW);
-            } else if (startState == NORMAL) {
-                animateOverviewPanelButtons(goingTowards == OVERVIEW);
-                animateScrim(goingTowards == OVERVIEW);
-            }
-        } else if (threshold == PinchThresholdManager.THRESHOLD_THREE) {
-            // Passing threshold 3 ends the pinch and snaps to the new state.
-            if (startState == OVERVIEW && goingTowards == NORMAL) {
-                mLauncher.getUserEventDispatcher().logActionOnContainer(
-                        Action.Touch.PINCH, Action.Direction.NONE,
-                        ContainerType.OVERVIEW, mWorkspace.getCurrentPage());
-                mLauncher.showWorkspace(true);
-                mWorkspace.snapToPage(mWorkspace.getCurrentPage());
-            } else if (startState == NORMAL && goingTowards == OVERVIEW) {
-                mLauncher.getUserEventDispatcher().logActionOnContainer(
-                        Action.Touch.PINCH, Action.Direction.NONE,
-                        ContainerType.WORKSPACE, mWorkspace.getCurrentPage());
-                mLauncher.showOverviewMode(true);
-            }
-        } else {
-            Log.e(TAG, "Received unknown threshold to animate: " + threshold);
-        }
-    }
-
-    private void setOverviewPanelsAlpha(int alpha, int duration) {
-        int childCount = mWorkspace.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
-            if (duration == 0) {
-                cl.getScrimBackground().setAlpha(alpha);
-            } else {
-                ObjectAnimator.ofInt(cl.getScrimBackground(),
-                        LauncherAnimUtils.DRAWABLE_ALPHA, alpha).setDuration(duration).start();
-            }
-        }
-    }
-
-    private void animateHotseatAndQsb(boolean show) {
-        startAnimator(INDEX_HOTSEAT,
-                mWorkspace.createHotseatAlphaAnimator(show ? 1 : 0), THRESHOLD_ANIM_DURATION);
-    }
-
-    private void animateOverviewPanelButtons(boolean show) {
-        animateShowHideView(INDEX_OVERVIEW_PANEL_BUTTONS, mLauncher.getOverviewPanel(), show);
-    }
-
-    private void animateScrim(boolean show) {
-        int endValue = show ? mWorkspace.getStateTransitionAnimation().mWorkspaceScrimAlpha : 0;
-        startAnimator(INDEX_SCRIM, ObjectAnimator.ofInt(
-                mLauncher.getDragLayer().getScrim(), LauncherAnimUtils.DRAWABLE_ALPHA, endValue),
-                mNormalOverviewTransitionDuration);
-    }
-
-    private void animateShowHideView(int index, final View view, boolean show) {
-        Animator animator = ObjectAnimator.ofFloat(view, View.ALPHA, show ? 1 : 0);
-        animator.addListener(new AnimationLayerSet(view));
-        if (show) {
-            view.setVisibility(View.VISIBLE);
-        } else {
-            animator.addListener(new AnimatorListenerAdapter() {
-                private boolean mCancelled = false;
-
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    mCancelled = true;
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (!mCancelled) {
-                        view.setVisibility(View.INVISIBLE);
-                    }
-                }
-            });
-        }
-        startAnimator(index, animator, THRESHOLD_ANIM_DURATION);
-    }
-
-    private void startAnimator(int index, Animator animator, long duration) {
-        if (mAnimators[index] != null) {
-            mAnimators[index].cancel();
-        }
-        mAnimators[index] = animator;
-        mAnimators[index].setInterpolator(INTERPOLATOR);
-        mAnimators[index].setDuration(duration).start();
-    }
-}
diff --git a/src/com/android/launcher3/PinchThresholdManager.java b/src/com/android/launcher3/PinchThresholdManager.java
deleted file mode 100644
index 4937633..0000000
--- a/src/com/android/launcher3/PinchThresholdManager.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-/**
- * Keeps track of when thresholds are passed during a pinch gesture,
- * used to inform {@link PinchAnimationManager} throughout.
- *
- * @see PinchToOverviewListener
- * @see PinchAnimationManager
- */
-public class PinchThresholdManager {
-    public static final float THRESHOLD_ZERO = 0.0f;
-    public static final float THRESHOLD_ONE = 0.40f;
-    public static final float THRESHOLD_TWO = 0.70f;
-    public static final float THRESHOLD_THREE = 0.95f;
-
-    private final Workspace mWorkspace;
-    private final Launcher mLauncher;
-
-    private float mPassedThreshold = THRESHOLD_ZERO;
-
-    public PinchThresholdManager(Workspace workspace) {
-        mWorkspace = workspace;
-        mLauncher = mWorkspace.mLauncher;
-    }
-
-    /**
-     * Uses the pinch progress to determine whether a threshold has been passed,
-     * and asks the {@param animationManager} to animate if so.
-     * @param progress From 0 to 1, where 0 is overview and 1 is workspace.
-     * @param animationManager Animates the threshold change if one is passed.
-     * @return The last passed threshold, one of
-     *         {@link PinchThresholdManager#THRESHOLD_ZERO},
-     *         {@link PinchThresholdManager#THRESHOLD_ONE},
-     *         {@link PinchThresholdManager#THRESHOLD_TWO}, or
-     *         {@link PinchThresholdManager#THRESHOLD_THREE}
-     */
-    public float updateAndAnimatePassedThreshold(float progress,
-            PinchAnimationManager animationManager) {
-        if (!mLauncher.isInState(LauncherState.OVERVIEW)) {
-            // Invert the progress, because going from workspace to overview is 1 to 0.
-            progress = 1f - progress;
-        }
-
-        float previousPassedThreshold = mPassedThreshold;
-
-        if (progress < THRESHOLD_ONE) {
-            mPassedThreshold = THRESHOLD_ZERO;
-        } else if (progress < THRESHOLD_TWO) {
-            mPassedThreshold = THRESHOLD_ONE;
-        } else if (progress < THRESHOLD_THREE) {
-            mPassedThreshold = THRESHOLD_TWO;
-        } else {
-            mPassedThreshold = THRESHOLD_THREE;
-        }
-
-        if (mPassedThreshold != previousPassedThreshold) {
-            LauncherState fromState = mLauncher.getWorkspace().getState();
-            LauncherState toState = mLauncher.isInState(LauncherState.OVERVIEW)
-                    ? LauncherState.NORMAL : LauncherState.OVERVIEW;
-            float thresholdToAnimate = mPassedThreshold;
-            if (mPassedThreshold < previousPassedThreshold) {
-                // User reversed pinch, so heading back to the state that they started from.
-                toState = fromState;
-                thresholdToAnimate = previousPassedThreshold;
-            }
-            animationManager.animateThreshold(thresholdToAnimate, fromState, toState);
-        }
-        return mPassedThreshold;
-    }
-
-    public float getPassedThreshold() {
-        return mPassedThreshold;
-    }
-
-    public void reset() {
-        mPassedThreshold = THRESHOLD_ZERO;
-    }
-}
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
index a1288b4..47113c9 100644
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ b/src/com/android/launcher3/PinchToOverviewListener.java
@@ -16,49 +16,52 @@
 
 package com.android.launcher3;
 
-import android.animation.TimeInterpolator;
-import android.content.Context;
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.util.Range;
 import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
 
 import com.android.launcher3.util.TouchController;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
 /**
  * Detects pinches and animates the Workspace to/from overview mode.
- *
- * Usage: Pass MotionEvents to onInterceptTouchEvent() and onTouchEvent(). This class will handle
- * the pinch detection, and use {@link PinchAnimationManager} to handle the animations.
- *
- * @see PinchThresholdManager
- * @see PinchAnimationManager
  */
-public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
-        implements TouchController {
-    private static final float OVERVIEW_PROGRESS = 0f;
-    private static final float WORKSPACE_PROGRESS = 1f;
+@TargetApi(Build.VERSION_CODES.O)
+public class PinchToOverviewListener
+        implements TouchController, OnScaleGestureListener, Runnable {
+
+    private static final float ACCEPT_THRESHOLD = 0.65f;
     /**
      * The velocity threshold at which a pinch will be completed instead of canceled,
-     * even if the first threshold has not been passed. Measured in progress / millisecond
+     * even if the first threshold has not been passed. Measured in scale / millisecond
      */
-    private static final float FLING_VELOCITY = 0.003f;
+    private static final float FLING_VELOCITY = 0.001f;
 
-    private ScaleGestureDetector mPinchDetector;
+    private final ScaleGestureDetector mPinchDetector;
     private Launcher mLauncher;
     private Workspace mWorkspace = null;
     private boolean mPinchStarted = false;
-    private float mPreviousProgress;
-    private float mProgressDelta;
-    private long mPreviousTimeMillis;
-    private long mTimeDelta;
-    private boolean mPinchCanceled = false;
-    private TimeInterpolator mInterpolator;
 
-    private PinchThresholdManager mThresholdManager;
-    private PinchAnimationManager mAnimationManager;
+    private AnimatorSet mCurrentAnimation;
+    private float mCurrentScale;
+    private Range<Integer> mDurationRange;
+    private boolean mShouldGoToFinalState;
+
+    private LauncherState mToState;
 
     public PinchToOverviewListener(Launcher launcher) {
         mLauncher = launcher;
-        mPinchDetector = new ScaleGestureDetector((Context) mLauncher, this);
+        mPinchDetector = new ScaleGestureDetector(mLauncher, this);
     }
 
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
@@ -67,24 +70,17 @@
     }
 
     public boolean onControllerTouchEvent(MotionEvent ev) {
-        if (mPinchStarted) {
-            if (ev.getPointerCount() > 2) {
-                // Using more than two fingers causes weird behavior, so just cancel the pinch.
-                cancelPinch(mPreviousProgress, -1);
-            } else {
-                return mPinchDetector.onTouchEvent(ev);
-            }
-        }
-        return false;
+        return mPinchDetector.onTouchEvent(ev);
     }
 
     @Override
     public boolean onScaleBegin(ScaleGestureDetector detector) {
-        if (!mLauncher.isInState(LauncherState.NORMAL)) {
+        if (!mLauncher.isInState(LauncherState.NORMAL)
+                && !mLauncher.isInState(LauncherState.OVERVIEW)) {
             // Don't listen for the pinch gesture if on all apps, widget picker, -1, etc.
             return false;
         }
-        if (mAnimationManager != null && mAnimationManager.isAnimating()) {
+        if (mCurrentAnimation != null) {
             // Don't listen for the pinch gesture if we are already animating from a previous one.
             return false;
         }
@@ -94,8 +90,6 @@
         }
         if (mWorkspace == null) {
             mWorkspace = mLauncher.getWorkspace();
-            mThresholdManager = new PinchThresholdManager(mWorkspace);
-            mAnimationManager = new PinchAnimationManager(mLauncher);
         }
         if (mWorkspace.isSwitchingState() || mWorkspace.mScrollInteractionBegan) {
             // Don't listen for the pinch gesture while switching state, as it will cause a jump
@@ -107,109 +101,87 @@
             return false;
         }
 
-        mPreviousProgress = mLauncher.isInState(LauncherState.OVERVIEW) ? OVERVIEW_PROGRESS : WORKSPACE_PROGRESS;
-        mPreviousTimeMillis = System.currentTimeMillis();
-        mInterpolator = mLauncher.isInState(LauncherState.OVERVIEW) ? new LogDecelerateInterpolator(100, 0)
-                : new LogAccelerateInterpolator(100, 0);
-        mPinchStarted = true;
-        mWorkspace.onPrepareStateTransition(true);
-        return true;
-    }
-
-    @Override
-    public void onScaleEnd(ScaleGestureDetector detector) {
-        super.onScaleEnd(detector);
-
-        float progressVelocity = mProgressDelta / mTimeDelta;
-        float passedThreshold = mThresholdManager.getPassedThreshold();
-        boolean isFling = mLauncher.isInState(LauncherState.OVERVIEW) && progressVelocity >= FLING_VELOCITY
-                || !mLauncher.isInState(LauncherState.OVERVIEW) && progressVelocity <= -FLING_VELOCITY;
-        boolean shouldCancelPinch = !isFling && passedThreshold < PinchThresholdManager.THRESHOLD_ONE;
-        // If we are going towards overview, mPreviousProgress is how much further we need to
-        // go, since it is going from 1 to 0. If we are going to workspace, we want
-        // 1 - mPreviousProgress.
-        float remainingProgress = mPreviousProgress;
-        if (mLauncher.isInState(LauncherState.OVERVIEW) || shouldCancelPinch) {
-            remainingProgress = 1f - mPreviousProgress;
-        }
-        int duration = computeDuration(remainingProgress, progressVelocity);
-        if (shouldCancelPinch) {
-            cancelPinch(mPreviousProgress, duration);
-        } else if (passedThreshold < PinchThresholdManager.THRESHOLD_THREE) {
-            float toProgress = mLauncher.isInState(LauncherState.OVERVIEW) ?
-                    WORKSPACE_PROGRESS : OVERVIEW_PROGRESS;
-            mAnimationManager.animateToProgress(mPreviousProgress, toProgress, duration,
-                    mThresholdManager);
-        } else {
-            mThresholdManager.reset();
-            mWorkspace.onEndStateTransition();
-        }
-        mPinchStarted = false;
-        mPinchCanceled = false;
-    }
-
-    /**
-     * Compute the amount of time required to complete the transition based on the current pinch
-     * speed. If this time is too long, instead return the normal duration, ignoring the speed.
-     */
-    private int computeDuration(float remainingProgress, float progressVelocity) {
-        float progressSpeed = Math.abs(progressVelocity);
-        int remainingMillis = (int) (remainingProgress / progressSpeed);
-        return Math.min(remainingMillis, mAnimationManager.getNormalOverviewTransitionDuration());
-    }
-
-    /**
-     * Cancels the current pinch, returning back to where the pinch started (either workspace or
-     * overview). If duration is -1, the default overview transition duration is used.
-     */
-    private void cancelPinch(float currentProgress, int duration) {
-        if (mPinchCanceled) return;
-        mPinchCanceled = true;
-        float toProgress = mLauncher.isInState(LauncherState.OVERVIEW) ? OVERVIEW_PROGRESS : WORKSPACE_PROGRESS;
-        mAnimationManager.animateToProgress(currentProgress, toProgress, duration,
-                mThresholdManager);
-        mPinchStarted = false;
-    }
-
-    @Override
-    public boolean onScale(ScaleGestureDetector detector) {
-        if (mThresholdManager.getPassedThreshold() == PinchThresholdManager.THRESHOLD_THREE) {
-            // We completed the pinch, so stop listening to further movement until user lets go.
-            return true;
-        }
         if (mLauncher.getDragController().isDragging()) {
             mLauncher.getDragController().cancelDrag();
         }
 
-        float pinchDist = detector.getCurrentSpan() - detector.getPreviousSpan();
-        if (pinchDist < 0 && mLauncher.isInState(LauncherState.OVERVIEW) ||
-                pinchDist > 0 && !mLauncher.isInState(LauncherState.OVERVIEW)) {
-            // Pinching the wrong way, so ignore.
-            return false;
-        }
-        // Pinch distance must equal the workspace width before switching states.
-        int pinchDistanceToCompleteTransition = mWorkspace.getWidth();
-        float overviewScale = mWorkspace.getOverviewModeShrinkFactor();
-        float initialWorkspaceScale = mLauncher.isInState(LauncherState.OVERVIEW) ? overviewScale : 1f;
-        float pinchScale = initialWorkspaceScale + pinchDist / pinchDistanceToCompleteTransition;
-        // Bound the scale between the overview scale and the normal workspace scale (1f).
-        pinchScale = Math.max(overviewScale, Math.min(pinchScale, 1f));
-        // Progress ranges from 0 to 1, where 0 corresponds to the overview scale and 1
-        // corresponds to the normal workspace scale (1f).
-        float progress = (pinchScale - overviewScale) / (1f - overviewScale);
-        float interpolatedProgress = mInterpolator.getInterpolation(progress);
+        mToState = mLauncher.isInState(LauncherState.OVERVIEW)
+                ? LauncherState.NORMAL : LauncherState.OVERVIEW;
+        mCurrentAnimation = mLauncher.mStateTransitionAnimation
+                .createAnimationToNewWorkspace(mToState, this);
+        mPinchStarted = true;
+        mCurrentScale = 1;
+        mDurationRange = Range.create(0, LauncherAnimUtils.OVERVIEW_TRANSITION_MS);
+        mShouldGoToFinalState = false;
 
-        mAnimationManager.setAnimationProgress(interpolatedProgress);
-        float passedThreshold = mThresholdManager.updateAndAnimatePassedThreshold(
-                interpolatedProgress, mAnimationManager);
-        if (passedThreshold == PinchThresholdManager.THRESHOLD_THREE) {
-            return true;
+        dispatchOnStart(mCurrentAnimation);
+        return true;
+    }
+
+    @Override
+    public void run() {
+        mCurrentAnimation = null;
+        mPinchStarted = false;
+    }
+
+    @Override
+    public void onScaleEnd(ScaleGestureDetector detector) {
+        if (mShouldGoToFinalState) {
+            mCurrentAnimation.start();
+        } else {
+            mCurrentAnimation.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (mToState == LauncherState.OVERVIEW) {
+                        mLauncher.showWorkspace(false);
+                    } else {
+                        mLauncher.showOverviewMode(false);
+                    }
+                }
+            });
+            mCurrentAnimation.reverse();
+        }
+    }
+
+    @Override
+    public boolean onScale(ScaleGestureDetector detector) {
+        mCurrentScale = detector.getScaleFactor() * mCurrentScale;
+
+        // If we are zooming out, inverse the mCurrentScale so that animationFraction = [0, 1]
+        // 0 => Animation complete
+        // 1=> Animation started
+        float animationFraction = mToState ==
+                LauncherState.OVERVIEW ? mCurrentScale : (1 / mCurrentScale);
+
+        float velocity = (1 - detector.getScaleFactor()) / detector.getTimeDelta();
+        if (Math.abs(velocity) >= FLING_VELOCITY) {
+            LauncherState toState = velocity > 0 ? LauncherState.OVERVIEW : LauncherState.NORMAL;
+            mShouldGoToFinalState = toState == mToState;
+        } else {
+            mShouldGoToFinalState = animationFraction <= ACCEPT_THRESHOLD;
         }
 
-        mProgressDelta = interpolatedProgress - mPreviousProgress;
-        mPreviousProgress = interpolatedProgress;
-        mTimeDelta = System.currentTimeMillis() - mPreviousTimeMillis;
-        mPreviousTimeMillis = System.currentTimeMillis();
-        return false;
+        // Move the transition animation to that duration.
+        long playPosition = mDurationRange.clamp(
+                (int) ((1 - animationFraction) * mDurationRange.getUpper()));
+        mCurrentAnimation.setCurrentPlayTime(playPosition);
+
+        return true;
+    }
+
+    private void dispatchOnStart(Animator animator) {
+        for (AnimatorListener l : nonNullList(animator.getListeners())) {
+            l.onAnimationStart(animator);
+        }
+
+        if (animator instanceof AnimatorSet) {
+            for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
+                dispatchOnStart(anim);
+            }
+        }
+    }
+
+    private static <T> List<T> nonNullList(ArrayList<T> list) {
+        return list == null ? Collections.<T>emptyList() : list;
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 8e215b0..af56fd7 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -18,6 +18,8 @@
 
 import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.SPRING_LOADED;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -187,18 +189,15 @@
         int finalBackgroundAlpha = state.hasScrim ? 255 : 0;
 
         final float finalWorkspaceTranslationY;
-        switch (state) {
-            case OVERVIEW:
-                mNewScale = mOverviewModeShrinkFactor;
-                finalWorkspaceTranslationY = mWorkspace.getOverviewModeTranslationY();
-                break;
-            case SPRING_LOADED:
-                mNewScale = mSpringLoadedShrinkFactor;
-                finalWorkspaceTranslationY = mWorkspace.getSpringLoadedTranslationY();
-                break;
-            default:
-                mNewScale = 1f;
-                finalWorkspaceTranslationY = 0;
+        if (state == OVERVIEW) {
+            mNewScale = mOverviewModeShrinkFactor;
+            finalWorkspaceTranslationY = mWorkspace.getOverviewModeTranslationY();
+        } else if (state == SPRING_LOADED) {
+            mNewScale = mSpringLoadedShrinkFactor;
+            finalWorkspaceTranslationY = mWorkspace.getSpringLoadedTranslationY();
+        } else {
+            mNewScale = 1f;
+            finalWorkspaceTranslationY = 0;
         }
 
         int toPage = mWorkspace.getPageNearestToCenterOfScreen();
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index bc5aafc..f2bad6b 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -143,7 +143,7 @@
 
     public void onAccessibilityStateChanged(boolean isAccessibilityEnabled) {
         mPinchListener = FeatureFlags.LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW || isAccessibilityEnabled
-                ? null : new PinchToOverviewListener(mLauncher);
+                || !Utilities.ATLEAST_OREO ? null : new PinchToOverviewListener(mLauncher);
     }
 
     public boolean isEventOverHotseat(MotionEvent ev) {