Merge "Removing extra binder call on state or visibility change" into ub-launcher3-edmonton
diff --git a/quickstep/res/layout/drag_handle_indicator.xml b/quickstep/res/layout/drag_handle_indicator.xml
deleted file mode 100644
index 9ee05d5..0000000
--- a/quickstep/res/layout/drag_handle_indicator.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<com.android.quickstep.views.QuickstepDragIndicator
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/drag_indicator"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:contentDescription="@string/accessibility_desc_recent_apps"
-    android:scaleType="centerInside"
-    android:tint="?attr/workspaceTextColor" />
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 9e4d60c..ad5f767 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -30,8 +30,9 @@
     <dimen name="quickstep_fling_min_velocity">250dp</dimen>
 
     <!-- Launcher app transition -->
-    <dimen name="content_trans_y">25dp</dimen>
-    <dimen name="workspace_trans_y">80dp</dimen>
+    <dimen name="content_trans_y">50dp</dimen>
+    <dimen name="workspace_trans_y">50dp</dimen>
+    <dimen name="closing_window_trans_y">115dp</dimen>
 
     <dimen name="recents_empty_message_text_size">16sp</dimen>
     <dimen name="recents_empty_message_text_padding">16dp</dimen>
@@ -50,5 +51,4 @@
     <dimen name="clear_all_container_width">168dp</dimen>
 
     <dimen name="shelf_surface_radius">16dp</dimen>
-    <dimen name="shelf_surface_top_padding">4dp</dimen>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 4963f5d..fc02f72 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -19,12 +19,11 @@
 import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
-import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
-import static com.android.launcher3.anim.Interpolators.APP_CLOSE_ALPHA;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.TaskUtils.findTaskViewToLaunch;
 import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
@@ -58,7 +57,6 @@
 
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.InsettableFrameLayout.LayoutParams;
-import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -102,11 +100,10 @@
 
     public static final int RECENTS_LAUNCH_DURATION = 336;
     private static final int LAUNCHER_RESUME_START_DELAY = 100;
-    private static final int CLOSING_TRANSITION_DURATION_MS = 350;
+    private static final int CLOSING_TRANSITION_DURATION_MS = 250;
 
     // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
     public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f;
-    public static final float ALL_APPS_PROGRESS_OVERSHOOT = 0.99581414f;
 
     private final DragLayer mDragLayer;
     private final Launcher mLauncher;
@@ -116,6 +113,7 @@
 
     private final float mContentTransY;
     private final float mWorkspaceTransY;
+    private final float mClosingWindowTransY;
 
     private DeviceProfile mDeviceProfile;
     private View mFloatingView;
@@ -129,6 +127,16 @@
         }
     };
 
+    private final Runnable mDragLayerResetRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mDragLayer.setLayerType(View.LAYER_TYPE_NONE, null);
+            mDragLayer.setAlpha(1);
+            mDragLayer.setTranslationY(0);
+            mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
+        }
+    };
+
     private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationStart(Animator animation) {
@@ -151,6 +159,7 @@
         Resources res = mLauncher.getResources();
         mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
         mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y);
+        mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
 
         mLauncher.addOnDeviceProfileChangeListener(this);
         registerRemoteAnimations();
@@ -187,7 +196,7 @@
                         anim.play(getIconAnimator(v));
                         if (launcherClosing) {
                             Pair<AnimatorSet, Runnable> launcherContentAnimator =
-                                    getLauncherContentAnimator(false /* show */);
+                                    getLauncherContentAnimator(true /* isAppOpening */);
                             anim.play(launcherContentAnimator.first);
                             anim.addListener(new AnimatorListenerAdapter() {
                                 @Override
@@ -281,21 +290,21 @@
     /**
      * Content is everything on screen except the background and the floating view (if any).
      *
-     * @param show If true: Animate the content so that it moves upwards and fades in.
-     *             Else: Animate the content so that it moves downwards and fades out.
+     * @param isAppOpening True when this is called when an app is opening.
+     *                     False when this is called when an app is closing.
      */
-    private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean show) {
+    private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening) {
         AnimatorSet launcherAnimator = new AnimatorSet();
         Runnable endListener;
 
-        float[] alphas = show
-                ? new float[] {0, 1}
-                : new float[] {1, 0};
-        float[] trans = show
-                ? new float[] {mContentTransY, 0,}
-                : new float[] {0, mContentTransY};
+        if (mLauncher.isInState(ALL_APPS)) {
+            float[] alphas = isAppOpening
+                    ? new float[] {1, 0}
+                    : new float[] {0, 1};
+            float[] trans = isAppOpening
+                    ? new float[] {0, mContentTransY}
+                    : new float[] {-mContentTransY, 0};
 
-        if (mLauncher.isInState(LauncherState.ALL_APPS) && !mDeviceProfile.isVerticalBarLayout()) {
             // All Apps in portrait mode is full screen, so we only animate AllAppsContainerView.
             final View appsView = mLauncher.getAppsView();
             final float startAlpha = appsView.getAlpha();
@@ -326,6 +335,9 @@
                 appsView.setLayerType(View.LAYER_TYPE_NONE, null);
             };
         } else {
+            float[] alphas = new float[] {1, 0};
+            float[] trans = new float[] {0, mContentTransY};
+
             mDragLayer.setAlpha(alphas[0]);
             mDragLayer.setTranslationY(trans[0]);
 
@@ -343,12 +355,7 @@
 
             // Pause page indicator animations as they lead to layer trashing.
             mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
-            endListener = () -> {
-                mDragLayer.setLayerType(View.LAYER_TYPE_NONE, null);
-                mDragLayer.setAlpha(1);
-                mDragLayer.setTranslationY(0);
-                mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
-            };
+            endListener = mDragLayerResetRunnable;
         }
         return new Pair<>(launcherAnimator, endListener);
     }
@@ -646,16 +653,14 @@
      */
     private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] targets) {
         Matrix matrix = new Matrix();
-        float height = mLauncher.getDeviceProfile().heightPx;
-        float width = mLauncher.getDeviceProfile().widthPx;
-        float endX = (mLauncher.<RecentsView>getOverviewPanel().isRtl() ? -width : width) * 1.16f;
-
         ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
-        closingAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
+        int duration = CLOSING_TRANSITION_DURATION_MS;
+        closingAnimator.setDuration(duration);
+        Rect crop = new Rect();
         closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
-            FloatProp mDx = new FloatProp(0, endX, 0, 350, AGGRESSIVE_EASE_IN_OUT);
-            FloatProp mScale = new FloatProp(1f, 0.8f, 0, 267, AGGRESSIVE_EASE);
-            FloatProp mAlpha = new FloatProp(1f, 0f, 0, 350, APP_CLOSE_ALPHA);
+            FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7);
+            FloatProp mScale = new FloatProp(1f, 1.075f, 0, duration, DEACCEL_1_7);
+            FloatProp mAlpha = new FloatProp(1f, 0f, 0, duration, DEACCEL_1_7);
 
             boolean isFirstFrame = true;
 
@@ -667,13 +672,16 @@
                     isFirstFrame = false;
                 }
                 for (RemoteAnimationTargetCompat app : targets) {
+                    crop.set(app.clipRect);
+                    crop.top = mDeviceProfile.getInsets().top;
                     if (app.mode == RemoteAnimationTargetCompat.MODE_CLOSING) {
                         t.setAlpha(app.leash, mAlpha.value);
                         matrix.setScale(mScale.value, mScale.value,
                                 app.sourceContainerBounds.centerX(),
                                 app.sourceContainerBounds.centerY());
-                        matrix.postTranslate(mDx.value, 0);
+                        matrix.postTranslate(0, mDy.value);
                         matrix.postTranslate(app.position.x, app.position.y);
+                        t.setWindowCrop(app.leash, crop);
                         t.setMatrix(app.leash, matrix);
                     }
                 }
@@ -694,7 +702,7 @@
         if (mLauncher.isInState(LauncherState.ALL_APPS)
                 || mLauncher.getDeviceProfile().isVerticalBarLayout()) {
             Pair<AnimatorSet, Runnable> contentAnimator =
-                    getLauncherContentAnimator(true /* show */);
+                    getLauncherContentAnimator(false /* isAppOpening */);
             contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY);
             anim.play(contentAnimator.first);
             anim.addListener(new AnimatorListenerAdapter() {
@@ -706,53 +714,29 @@
         } else {
             AnimatorSet workspaceAnimator = new AnimatorSet();
 
-            mLauncher.getWorkspace().setTranslationY(mWorkspaceTransY);
-            workspaceAnimator.play(ObjectAnimator.ofFloat(mLauncher.getWorkspace(),
-                    View.TRANSLATION_Y, mWorkspaceTransY, 0));
+            mDragLayer.setTranslationY(-mWorkspaceTransY);
+            workspaceAnimator.play(ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y,
+                    -mWorkspaceTransY, 0));
 
-            View currentPage = ((CellLayout) mLauncher.getWorkspace()
-                    .getChildAt(mLauncher.getWorkspace().getCurrentPage()))
-                    .getShortcutsAndWidgets();
-            currentPage.setAlpha(0f);
-            workspaceAnimator.play(ObjectAnimator.ofFloat(currentPage, View.ALPHA, 0, 1f));
+            mDragLayer.setAlpha(0);
+            workspaceAnimator.play(ObjectAnimator.ofFloat(mDragLayer, View.ALPHA, 0, 1f));
 
             workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
             workspaceAnimator.setDuration(333);
-            workspaceAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-            currentPage.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            workspaceAnimator.setInterpolator(Interpolators.DEACCEL_1_7);
+
+            // Pause page indicator animations as they lead to layer trashing.
+            mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
+            mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
             workspaceAnimator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    currentPage.setLayerType(View.LAYER_TYPE_NONE, null);
+                    mDragLayerResetRunnable.run();
                 }
             });
-
-            // Animate the shelf in two parts: slide in, and overeshoot.
-            AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
-            // The shelf will start offscreen
-            final float startY = ALL_APPS_PROGRESS_OFF_SCREEN;
-            // And will end slightly pulled up, so that there is something to overshoot back to 1f.
-            final float slideEnd = ALL_APPS_PROGRESS_OVERSHOOT;
-
-            allAppsController.setProgress(startY);
-
-            Animator allAppsSlideIn =
-                    ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS, startY, slideEnd);
-            allAppsSlideIn.setStartDelay(LAUNCHER_RESUME_START_DELAY);
-            allAppsSlideIn.setDuration(317);
-            allAppsSlideIn.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-
-            Animator allAppsOvershoot =
-                    ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS, slideEnd, 1f);
-            allAppsOvershoot.setDuration(153);
-            allAppsOvershoot.setInterpolator(Interpolators.OVERSHOOT_0);
-
             anim.play(workspaceAnimator);
-            anim.playSequentially(allAppsSlideIn, allAppsOvershoot);
-            anim.addListener(mReapplyStateListener);
         }
     }
-
     private boolean hasControlRemoteAppTransitionPermission() {
         return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
                 == PackageManager.PERMISSION_GRANTED;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index e31805c..b0f8d99 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -84,7 +84,7 @@
 
     @Override
     public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
-        return new float[] {1f, -0.2f};
+        return new float[] {0.9f, -0.2f};
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
index 42f6c74..ce8192f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
@@ -9,6 +9,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationComponents;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -56,11 +57,11 @@
     }
 
     @Override
-    protected float initCurrentAnimation() {
+    protected float initCurrentAnimation(@AnimationComponents int animComponent) {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);
-        mCurrentAnimation = mLauncher.getStateManager()
-                .createAnimationToNewWorkspace(mToState, maxAccuracy);
+        mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
+                maxAccuracy, animComponent);
         return (mLauncher.getDeviceProfile().isSeascape() ? 2 : -2) / range;
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 91e1e7f..4029b82 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -25,6 +25,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.views.RecentsView;
@@ -47,8 +48,12 @@
 
     @Override
     public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
-        // TODO: provide a valid value
-        return new float[]{1, 0, -launcher.getDeviceProfile().hotseatBarSizePx / 2};
+        RecentsView recentsView = launcher.getOverviewPanel();
+        Workspace workspace = launcher.getWorkspace();
+        recentsView.getTaskSize(sTempRect);
+        float scale = (float) sTempRect.width() / workspace.getWidth();
+        float parallaxFactor = 0.4f;
+        return new float[]{scale, 0, -getDefaultSwipeHeight(launcher) * parallaxFactor};
     }
 
     @Override
@@ -92,9 +97,9 @@
     @Override
     public int getVisibleElements(Launcher launcher) {
         if (launcher.getDeviceProfile().isVerticalBarLayout()) {
-            return DRAG_HANDLE_INDICATOR;
+            return 0;
         } else {
-            return HOTSEAT_SEARCH_BOX | DRAG_HANDLE_INDICATOR |
+            return HOTSEAT_SEARCH_BOX |
                     (launcher.getAppsView().getFloatingHeaderView().hasVisibleContent()
                             ? ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS);
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
index 2f0bdc6..514c0e8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -18,20 +18,20 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.view.MotionEvent;
 import android.view.animation.Interpolator;
+import android.view.animation.OvershootInterpolator;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationComponents;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
@@ -52,43 +52,11 @@
 
     private static final String TAG = "PortraitStatesTouchCtrl";
 
-    private static final float TOTAL_DISTANCE_MULTIPLIER = 3f;
-    private static final float LINEAR_SCALE_LIMIT = 1 / TOTAL_DISTANCE_MULTIPLIER;
-
-    // Must be greater than LINEAR_SCALE_LIMIT;
-    private static final float MAXIMUM_DISTANCE_FACTOR = 0.9f;
-
-    // Maximum amount to overshoot.
-    private static final float MAX_OVERSHOOT = 0.3f;
-
-    private static final double PI_BY_2 = Math.PI / 2;
-
     private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
 
     // If true, we will finish the current animation instantly on second touch.
     private boolean mFinishFastOnSecondTouch;
 
-    private final Interpolator mAllAppsDampedInterpolator = new Interpolator() {
-
-        private final double mAngleMultiplier = Math.PI /
-                (2 * (MAXIMUM_DISTANCE_FACTOR - LINEAR_SCALE_LIMIT));
-
-        @Override
-        public float getInterpolation(float v) {
-            if (v <= LINEAR_SCALE_LIMIT) {
-                return v * TOTAL_DISTANCE_MULTIPLIER;
-            }
-            float overshoot = (v - LINEAR_SCALE_LIMIT);
-            return (float) (1 + MAX_OVERSHOOT * Math.sin(overshoot * mAngleMultiplier));
-        }
-    };
-
-    private final Interpolator mOverviewBoundInterpolator = (v) -> {
-            if (v >= MAXIMUM_DISTANCE_FACTOR) {
-                return 1;
-            }
-            return FAST_OUT_SLOW_IN.getInterpolation(v / MAXIMUM_DISTANCE_FACTOR);
-    };
 
     public PortraitStatesTouchController(Launcher l) {
         super(l, SwipeDetector.VERTICAL);
@@ -144,17 +112,16 @@
     }
 
     private AnimatorSetBuilder getNormalToOverviewAnimation() {
-        mAllAppsInterpolatorWrapper.baseInterpolator = mAllAppsDampedInterpolator;
+        mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;
 
         AnimatorSetBuilder builder = new AnimatorSetBuilder();
         builder.setInterpolator(ANIM_VERTICAL_PROGRESS, mAllAppsInterpolatorWrapper);
 
-        builder.setInterpolator(ANIM_OVERVIEW_TRANSLATION, mOverviewBoundInterpolator);
         return builder;
     }
 
     @Override
-    protected float initCurrentAnimation() {
+    protected float initCurrentAnimation(@AnimationComponents int animComponents) {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);
 
@@ -167,7 +134,6 @@
 
         if (mFromState == NORMAL && mToState == OVERVIEW && totalShift != 0) {
             builder = getNormalToOverviewAnimation();
-            totalShift = totalShift * TOTAL_DISTANCE_MULTIPLIER;
         } else {
             builder = new AnimatorSetBuilder();
         }
@@ -190,7 +156,8 @@
             mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
         } else {
             mCurrentAnimation = mLauncher.getStateManager()
-                    .createAnimationToNewWorkspace(mToState, builder, maxAccuracy, this::clearState);
+                    .createAnimationToNewWorkspace(mToState, builder, maxAccuracy, this::clearState,
+                            animComponents);
         }
 
         if (totalShift == 0) {
@@ -210,9 +177,9 @@
     @Override
     protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
             LauncherState targetState, float velocity, boolean isFling) {
-        handleFirstSwipeToOverview(animator, expectedDuration, targetState, velocity, isFling);
         super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
                 velocity, isFling);
+        handleFirstSwipeToOverview(animator, expectedDuration, targetState, velocity, isFling);
     }
 
     private void handleFirstSwipeToOverview(final ValueAnimator animator,
@@ -220,62 +187,22 @@
             final boolean isFling) {
         if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
             mFinishFastOnSecondTouch = true;
-
-            // Update all apps interpolator
-            float currentFraction = mCurrentAnimation.getProgressFraction();
-            float absVelocity = Math.abs(velocity);
-            float currentValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction);
-
-            if (isFling && absVelocity > 1 && currentFraction < LINEAR_SCALE_LIMIT) {
-
-                // TODO: Clean up these magic calculations
-                // Linearly interpolate the max value based on the velocity.
-                float maxValue = Math.max(absVelocity > 4 ? 1 + MAX_OVERSHOOT :
-                                1 + (absVelocity - 1) * MAX_OVERSHOOT / 3,
-                        currentValue);
-                double angleToPeak = PI_BY_2 - Math.asin(currentValue / maxValue);
-
-                if (expectedDuration != 0 && angleToPeak != 0) {
-
-                    float distanceLeft = 1 - currentFraction;
-                    mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> {
-                        float scaledF = (f - currentFraction) / distanceLeft;
-
-                        if (scaledF < 0.5f) {
-                            double angle = PI_BY_2 - angleToPeak + scaledF * angleToPeak / 0.5f;
-                            return (float) (maxValue * Math.sin(angle));
-                        }
-
-                        scaledF = ((scaledF - .5f) / .5f);
-                        double angle = PI_BY_2 + 3 * scaledF * PI_BY_2;
-                        float amplitude = (1 - scaledF) * (1 - scaledF) * (maxValue - 1);
-                        return 1 + (float) (amplitude * Math.sin(angle));
-                    };
-
-                    animator.setDuration(expectedDuration).setInterpolator(LINEAR);
-                    return;
-                }
+            if (isFling && expectedDuration != 0) {
+                // Update all apps interpolator to add a bit of overshoot starting from currFraction
+                final float currFraction = mCurrentAnimation.getProgressFraction();
+                mAllAppsInterpolatorWrapper.baseInterpolator
+                        = new OvershootInterpolator(Math.min(Math.abs(velocity) / 3, 3f)) {
+                    @Override
+                    public float getInterpolation(float t) {
+                        return super.getInterpolation(t) + ((1 - t) * currFraction);
+                    }
+                };
+                animator.setFloatValues(0, 1);
+                animator.setDuration(Math.max(expectedDuration, 300)).setInterpolator(LINEAR);
             }
-
-            if (currentFraction < LINEAR_SCALE_LIMIT) {
-                mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;
-                return;
-            }
-            float extraValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction) - 1;
-            float distanceLeft = 1 - currentFraction;
-
-            animator.setFloatValues(currentFraction, 1);
-            mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> {
-                float scaledF = (f - currentFraction) / distanceLeft;
-
-                double angle = scaledF * 1.5 * Math.PI;
-                float amplitude = (1 - scaledF) * (1 - scaledF) * extraValue;
-                return 1 + (float) (amplitude * Math.sin(angle));
-            };
-            animator.setDuration(200).setInterpolator(LINEAR);
-            return;
+        } else {
+            mFinishFastOnSecondTouch = false;
         }
-        mFinishFastOnSecondTouch = false;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 2579bc2..febe360 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
@@ -63,12 +62,14 @@
     @Override
     public void setStateWithAnimation(final LauncherState toState,
             AnimatorSetBuilder builder, AnimationConfig config) {
+        if (!config.playAtomicComponent()) {
+            // The entire recents animation is played atomically.
+            return;
+        }
         PropertySetter setter = config.getPropertySetter(builder);
         float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher);
-        setter.setFloat(mRecentsView, ADJACENT_SCALE, scaleTranslationYFactor[0],
-                builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
-        setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR, scaleTranslationYFactor[1],
-                builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
+        setter.setFloat(mRecentsView, ADJACENT_SCALE, scaleTranslationYFactor[0], LINEAR);
+        setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR, scaleTranslationYFactor[1], LINEAR);
         setter.setFloat(mRecentsViewContainer, CONTENT_ALPHA, toState.overviewUi ? 1 : 0,
                 AGGRESSIVE_EASE_IN_OUT);
 
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 9416a29..ad4af62 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -203,7 +203,12 @@
     }
 
     private boolean shouldIgnoreSwipeUpEnabledSettings() {
-        String sdkInt = getSystemProperty("ro.product.first_api_level", "0");
+        int deviceApiLevel = Build.VERSION.SDK_INT;
+
+        // Note: on factory ROM devices, this first_api_level property is intentionally not set.
+        // deviceApiLevel is used in these case.
+        String sdkInt = getSystemProperty("ro.product.first_api_level",
+                Integer.toString(deviceApiLevel));
         try {
             return Integer.parseInt(sdkInt) >= Build.VERSION_CODES.P;
         } catch (Exception e) {
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index b1fa5e2..2ff70dd 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -43,8 +43,7 @@
             extraSpace = 0;
         } else {
             Resources res = context.getResources();
-            extraSpace = dp.hotseatBarSizePx + res.getDimension(R.dimen.shelf_surface_top_padding)
-                    + res.getDimension(R.dimen.shelf_surface_radius);
+            extraSpace = dp.hotseatBarSizePx + res.getDimension(R.dimen.vertical_drag_handle_size);
         }
         calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect);
     }
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
index 34f580b..48b07a7 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
@@ -15,7 +15,9 @@
  */
 package com.android.quickstep.util;
 
-import android.animation.TimeInterpolator;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
 import android.graphics.PixelFormat;
@@ -30,7 +32,7 @@
 
 public class TaskViewDrawable extends Drawable {
 
-    public static FloatProperty<TaskViewDrawable> PROGRESS =
+    public static final FloatProperty<TaskViewDrawable> PROGRESS =
             new FloatProperty<TaskViewDrawable>("progress") {
                 @Override
                 public void setValue(TaskViewDrawable taskViewDrawable, float v) {
@@ -43,8 +45,10 @@
                 }
             };
 
-    private static final TimeInterpolator ICON_SIZE_INTERPOLATOR =
-            (t) -> (Math.max(t, 0.3f) - 0.3f) / 0.7f;
+    /**
+     * The progress at which we play the atomic icon scale animation.
+     */
+    private static final float ICON_SCALE_THRESHOLD = 0.95f;
 
     private final RecentsView mParent;
     private final View mIconView;
@@ -55,11 +59,15 @@
     private final ClipAnimationHelper mClipAnimationHelper;
 
     private float mProgress = 1;
+    private boolean mPassedIconScaleThreshold;
+    private ValueAnimator mIconScaleAnimator;
+    private float mIconScale;
 
     public TaskViewDrawable(TaskView tv, RecentsView parent) {
         mParent = parent;
         mIconView = tv.getIconView();
         mIconPos = new int[2];
+        mIconScale = mIconView.getScaleX();
         Utilities.getDescendantCoordRelativeToAncestor(mIconView, parent, mIconPos, true);
 
         mThumbnailView = tv.getThumbnail();
@@ -70,6 +78,37 @@
     public void setProgress(float progress) {
         mProgress = progress;
         mParent.invalidate();
+        boolean passedIconScaleThreshold = progress <= ICON_SCALE_THRESHOLD;
+        if (mPassedIconScaleThreshold != passedIconScaleThreshold) {
+            mPassedIconScaleThreshold = passedIconScaleThreshold;
+            animateIconScale(mPassedIconScaleThreshold ? 0 : 1);
+        }
+    }
+
+    private void animateIconScale(float toScale) {
+        if (mIconScaleAnimator != null) {
+            mIconScaleAnimator.cancel();
+        }
+        mIconScaleAnimator = ValueAnimator.ofFloat(mIconScale, toScale);
+        mIconScaleAnimator.addUpdateListener(valueAnimator -> {
+            mIconScale = (float) valueAnimator.getAnimatedValue();
+            if (mProgress > ICON_SCALE_THRESHOLD) {
+                // Speed up the icon scale to ensure it is 1 when progress is 1.
+                float iconProgress = (mProgress - ICON_SCALE_THRESHOLD) / (1 - ICON_SCALE_THRESHOLD);
+                if (iconProgress > mIconScale) {
+                    mIconScale = iconProgress;
+                }
+            }
+            invalidateSelf();
+        });
+        mIconScaleAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mIconScaleAnimator = null;
+            }
+        });
+        mIconScaleAnimator.setDuration(TaskView.SCALE_ICON_DURATION);
+        mIconScaleAnimator.start();
     }
 
     @Override
@@ -81,8 +120,7 @@
 
         canvas.save();
         canvas.translate(mIconPos[0], mIconPos[1]);
-        float scale = ICON_SIZE_INTERPOLATOR.getInterpolation(mProgress);
-        canvas.scale(scale, scale, mIconView.getWidth() / 2, mIconView.getHeight() / 2);
+        canvas.scale(mIconScale, mIconScale, mIconView.getWidth() / 2, mIconView.getHeight() / 2);
         mIconView.draw(canvas);
         canvas.restore();
     }
diff --git a/quickstep/src/com/android/quickstep/views/QuickstepDragIndicator.java b/quickstep/src/com/android/quickstep/views/QuickstepDragIndicator.java
deleted file mode 100644
index 5e9cd6e..0000000
--- a/quickstep/src/com/android/quickstep/views/QuickstepDragIndicator.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.quickstep.views;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
-
-import com.android.launcher3.R;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
-import com.android.launcher3.views.LauncherDragIndicator;
-
-public class QuickstepDragIndicator extends LauncherDragIndicator {
-
-    public QuickstepDragIndicator(Context context) {
-        super(context);
-    }
-
-    public QuickstepDragIndicator(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public QuickstepDragIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    private boolean isInOverview() {
-        return mLauncher.isInState(OVERVIEW);
-    }
-
-    @Override
-    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
-        super.onInitializeAccessibilityNodeInfo(info);
-        info.setContentDescription(getContext().getString(R.string.all_apps_button_label));
-    }
-
-    @Override
-    protected void initCustomActions(AccessibilityNodeInfo info) {
-        if (!isInOverview()) {
-            super.initCustomActions(info);
-        }
-    }
-
-    @Override
-    public void onClick(View view) {
-        mLauncher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
-                ControlType.ALL_APPS_BUTTON,
-                isInOverview() ? ContainerType.TASKSWITCHER : ContainerType.WORKSPACE);
-        mLauncher.getStateManager().goToState(ALL_APPS);
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 92f8aef..5035721 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 
+import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
@@ -965,15 +966,13 @@
         if (currTask == null) {
             return;
         }
-        currTask.setScaleX(mAdjacentScale);
-        currTask.setScaleY(mAdjacentScale);
+        currTask.setZoomScale(mAdjacentScale);
 
         if (mCurrentPage - 1 >= 0) {
             TaskView adjacentTask = getPageAt(mCurrentPage - 1);
             float[] scaleAndTranslation = getAdjacentScaleAndTranslation(currTask, adjacentTask,
                     mAdjacentScale, 0);
-            adjacentTask.setScaleX(scaleAndTranslation[0]);
-            adjacentTask.setScaleY(scaleAndTranslation[0]);
+            adjacentTask.setZoomScale(scaleAndTranslation[0]);
             adjacentTask.setTranslationX(-scaleAndTranslation[1]);
             adjacentTask.setTranslationY(scaleAndTranslation[2]);
         }
@@ -981,8 +980,7 @@
             TaskView adjacentTask = getPageAt(mCurrentPage + 1);
             float[] scaleAndTranslation = getAdjacentScaleAndTranslation(currTask, adjacentTask,
                     mAdjacentScale, 0);
-            adjacentTask.setScaleX(scaleAndTranslation[0]);
-            adjacentTask.setScaleY(scaleAndTranslation[0]);
+            adjacentTask.setZoomScale(scaleAndTranslation[0]);
             adjacentTask.setTranslationX(scaleAndTranslation[1]);
             adjacentTask.setTranslationY(scaleAndTranslation[2]);
         }
@@ -991,7 +989,7 @@
     private float[] getAdjacentScaleAndTranslation(TaskView currTask, TaskView adjacentTask,
             float currTaskToScale, float currTaskToTranslationY) {
         float displacement = currTask.getWidth() * (currTaskToScale - currTask.getCurveScale());
-        sTempFloatArray[0] = currTaskToScale * adjacentTask.getCurveScale();
+        sTempFloatArray[0] = currTaskToScale;
         sTempFloatArray[1] = mIsRtl ? -displacement : displacement;
         sTempFloatArray[2] = currTaskToTranslationY;
         return sTempFloatArray;
@@ -1132,13 +1130,15 @@
         return anim;
     }
 
-    private ObjectAnimator createAnimForChild(View child, float[] toScaleAndTranslation) {
-        return ObjectAnimator.ofPropertyValuesHolder(child,
+    private Animator createAnimForChild(TaskView child, float[] toScaleAndTranslation) {
+        AnimatorSet anim = new AnimatorSet();
+        anim.play(ObjectAnimator.ofFloat(child, TaskView.ZOOM_SCALE, toScaleAndTranslation[0]));
+        anim.play(ObjectAnimator.ofPropertyValuesHolder(child,
                         new PropertyListBuilder()
-                                .scale(child.getScaleX() * toScaleAndTranslation[0])
                                 .translationX(toScaleAndTranslation[1])
                                 .translationY(toScaleAndTranslation[2])
-                                .build());
+                                .build()));
+        return anim;
     }
 
     public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) {
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index aa04672..b47af2d 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -31,7 +31,6 @@
 import android.util.AttributeSet;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.uioverrides.OverviewState;
 import com.android.launcher3.util.Themes;
@@ -49,8 +48,6 @@
     private static final int THRESHOLD_ALPHA_DARK = 102;
     private static final int THRESHOLD_ALPHA_LIGHT = 46;
 
-    private final Launcher mLauncher;
-
     // In transposed layout, we simply draw a flat color.
     private boolean mDrawingFlatColor;
 
@@ -58,7 +55,6 @@
     private final int mEndAlpha;
     private final int mThresholdAlpha;
     private final float mRadius;
-    private final float mTopPadding;
     private final float mMaxScrimAlpha;
     private final Paint mPaint;
 
@@ -77,15 +73,12 @@
 
     public ShelfScrimView(Context context, AttributeSet attrs) {
         super(context, attrs);
-
-        mLauncher = Launcher.getLauncher(context);
         mMaxScrimAlpha = OVERVIEW.getWorkspaceScrimAlpha(mLauncher);
 
         mEndAlpha = Color.alpha(mEndScrim);
         mThresholdAlpha = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark)
                 ? THRESHOLD_ALPHA_DARK : THRESHOLD_ALPHA_LIGHT;
         mRadius = mLauncher.getResources().getDimension(R.dimen.shelf_surface_radius);
-        mTopPadding = mLauncher.getResources().getDimension(R.dimen.shelf_surface_top_padding);
         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 
         // Just assume the easiest UI for now, until we have the proper layout information.
@@ -110,10 +103,10 @@
             mRemainingScreenPathValid = false;
             updateColors();
         }
+        updateDragHandleAlpha();
         invalidate();
     }
 
-
     @Override
     public void updateColors() {
         super.updateColors();
@@ -131,6 +124,7 @@
                         (1 - mProgress) / (1 - mMoveThreshold)));
                 mShelfColor = setAlphaComponent(mEndScrim, alpha);
             }
+
             mRemainingScreenColor = 0;
         } else if (mProgress <= 0) {
             mScrimMoveFactor = 0;
@@ -150,23 +144,42 @@
     }
 
     @Override
+    protected void updateDragHandleAlpha() {
+        if (mDrawingFlatColor) {
+            super.updateDragHandleAlpha();
+        } else if (mDragHandle != null) {
+            mDragHandle.setAlpha(255);
+        }
+    }
+
+    @Override
     protected void onDraw(Canvas canvas) {
+        float translate = drawBackground(canvas);
+
+        if (mDragHandle != null) {
+            canvas.translate(0, -translate);
+            mDragHandle.draw(canvas);
+            canvas.translate(0, translate);
+        }
+    }
+
+    private float drawBackground(Canvas canvas) {
         if (mDrawingFlatColor) {
             if (mCurrentFlatColor != 0) {
                 canvas.drawColor(mCurrentFlatColor);
             }
-            return;
+            return 0;
         }
 
         if (mShelfColor == 0) {
-            return;
+            return 0;
         } else if (mScrimMoveFactor <= 0) {
             canvas.drawColor(mShelfColor);
-            return;
+            return getHeight();
         }
 
         float minTop = getHeight() - mMinSize;
-        float top = minTop * mScrimMoveFactor - mTopPadding - mRadius;
+        float top = minTop * mScrimMoveFactor - mDragHandleSize;
 
         // Draw the scrim over the remaining screen if needed.
         if (mRemainingScreenColor != 0) {
@@ -192,5 +205,6 @@
         mPaint.setColor(mShelfColor);
         canvas.drawRoundRect(0, top, getWidth(), getHeight() + mRadius,
                 mRadius, mRadius, mPaint);
+        return minTop - mDragHandleSize - top;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index b8b9196..4f447b1 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -30,14 +30,15 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.util.AttributeSet;
+import android.util.FloatProperty;
 import android.util.Log;
+import android.util.Property;
 import android.view.View;
 import android.view.ViewOutlineProvider;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
-import android.widget.ImageView;
-
 import android.widget.Toast;
+
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
@@ -76,12 +77,26 @@
      */
     private static final float EDGE_SCALE_DOWN_FACTOR = 0.03f;
 
-    private static final long SCALE_ICON_DURATION = 120;
+    public static final long SCALE_ICON_DURATION = 120;
+
+    public static final Property<TaskView, Float> ZOOM_SCALE =
+            new FloatProperty<TaskView>("zoomScale") {
+                @Override
+                public void setValue(TaskView taskView, float v) {
+                    taskView.setZoomScale(v);
+                }
+
+                @Override
+                public Float get(TaskView taskView) {
+                    return taskView.mZoomScale;
+                }
+            };
 
     private Task mTask;
     private TaskThumbnailView mSnapshotView;
     private IconView mIconView;
     private float mCurveScale;
+    private float mZoomScale;
     private float mCurveDimAlpha;
     private Animator mDimAlphaAnim;
 
@@ -207,8 +222,7 @@
     }
 
     public void resetVisualProperties() {
-        setScaleX(1f);
-        setScaleY(1f);
+        setZoomScale(1);
         setTranslationX(0f);
         setTranslationY(0f);
         setTranslationZ(0);
@@ -226,9 +240,7 @@
             mSnapshotView.setDimAlpha(mCurveDimAlpha);
         }
 
-        mCurveScale = getCurveScaleForCurveInterpolation(curveInterpolation);
-        setScaleX(mCurveScale);
-        setScaleY(mCurveScale);
+        setCurveScale(getCurveScaleForCurveInterpolation(curveInterpolation));
     }
 
     @Override
@@ -247,10 +259,26 @@
         return 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
     }
 
+    private void setCurveScale(float curveScale) {
+        mCurveScale = curveScale;
+        onScaleChanged();
+    }
+
     public float getCurveScale() {
         return mCurveScale;
     }
 
+    public void setZoomScale(float adjacentScale) {
+        mZoomScale = adjacentScale;
+        onScaleChanged();
+    }
+
+    private void onScaleChanged() {
+        float scale = mCurveScale * mZoomScale;
+        setScaleX(scale);
+        setScaleY(scale);
+    }
+
     @Override
     public boolean hasOverlappingRendering() {
         // TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
diff --git a/res/animator-v23/discovery_bounce.xml b/res/animator-v23/discovery_bounce.xml
index 8d0e8fd..f554853 100644
--- a/res/animator-v23/discovery_bounce.xml
+++ b/res/animator-v23/discovery_bounce.xml
@@ -26,14 +26,14 @@
             android:fraction="0"
             android:value="1f" />
         <keyframe
-            android:fraction="0.346"
+            android:fraction="0.246"
             android:value="1f" />
         <keyframe
             android:fraction=".423"
             android:interpolator="@interpolator/disco_bounce"
-            android:value="0.9438f" />
+            android:value="0.9738f" />
         <keyframe
-            android:fraction="0.654"
+            android:fraction="0.754"
             android:interpolator="@interpolator/disco_bounce"
             android:value="1f" />
         <keyframe
diff --git a/res/drawable/all_apps_handle_landscape.xml b/res/drawable/drag_handle_indicator.xml
similarity index 77%
rename from res/drawable/all_apps_handle_landscape.xml
rename to res/drawable/drag_handle_indicator.xml
index 15518ff..b01b84a 100644
--- a/res/drawable/all_apps_handle_landscape.xml
+++ b/res/drawable/drag_handle_indicator.xml
@@ -15,25 +15,25 @@
 -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="@dimen/dynamic_grid_min_page_indicator_size"
-    android:height="@dimen/dynamic_grid_min_page_indicator_size"
-    android:viewportWidth="48.0"
-    android:viewportHeight="48.0" >
+    android:width="@dimen/vertical_drag_handle_size"
+    android:height="@dimen/vertical_drag_handle_size"
+    android:viewportWidth="36.0"
+    android:viewportHeight="36.0" >
 
     <group
-        android:translateX="17.5"
-        android:translateY="17.5">
+        android:translateX="11.5"
+        android:translateY="11.5">
         <path
             android:pathData="M2 8.5L6.5 4L11 8.5"
             android:strokeColor="?attr/workspaceAmbientShadowColor"
-            android:strokeWidth="4"
+            android:strokeWidth="3.6"
             android:strokeLineCap="round"
             android:strokeLineJoin="round" />
 
         <path
             android:pathData="M2 8.5L6.5 4L11 8.5"
             android:strokeColor="?attr/workspaceTextColor"
-            android:strokeWidth="2"
+            android:strokeWidth="1.8"
             android:strokeLineCap="round"
             android:strokeLineJoin="round" />
         </group>
diff --git a/res/drawable/ic_drag_indicator.xml b/res/drawable/ic_drag_indicator.xml
deleted file mode 100644
index d50bdd3..0000000
--- a/res/drawable/ic_drag_indicator.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:height="2dp"
-        android:width="16dp"
-        android:viewportHeight="2.0"
-        android:viewportWidth="16.0">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M1,0h14c0.55,0,1,0.45,1,1s-0.45,1-1,1H1C0.45,2,0,1.55,0,1S0.45,0,1,0z"/>
-</vector>
\ No newline at end of file
diff --git a/res/layout/drag_handle_indicator.xml b/res/layout/drag_handle_indicator.xml
deleted file mode 100644
index d5a7b8a..0000000
--- a/res/layout/drag_handle_indicator.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<com.android.launcher3.views.LauncherDragIndicator
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/drag_indicator"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:contentDescription="@string/all_apps_button_label"
-    android:scaleType="centerInside"
-    android:tint="?attr/workspaceTextColor" />
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index cd8a425..ec8bd5c 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -67,9 +67,6 @@
             android:layout_height="match_parent"
             android:visibility="invisible" />
 
-        <include android:id="@+id/drag_indicator"
-            layout="@layout/drag_handle_indicator" />
-
         <!-- DO NOT CHANGE THE ID -->
         <include
             android:id="@+id/hotseat"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index b1ad11e..cd050e8 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -42,6 +42,7 @@
     <dimen name="all_apps_scrim_radius">8dp</dimen>
     <dimen name="all_apps_scrim_margin">8dp</dimen>
     <dimen name="all_apps_scrim_blur">4dp</dimen>
+    <dimen name="vertical_drag_handle_size">24dp</dimen>
 
 <!-- Drop target bar -->
     <dimen name="dynamic_grid_drop_target_size">48dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5dc7844..65fffd3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -310,7 +310,7 @@
 
     <!-- Accessibility action to show quick actions menu for an icon. [CHAR_LIMIT=30] -->
     <string name="action_deep_shortcut">Shortcuts</string>
-    <!-- Accessibility description when the shortcuts menu has notifications as well as shortcuts. [CHAR_LIMIT=50] -->
+    <!-- Accessibility description when the context menu of a launcher icon that has notifications as well as shortcuts (providing quick access to app's actions). The "shortcuts" translation should be consistent with the one for action_deep_shortcut. [CHAR_LIMIT=50] -->
     <string name="shortcuts_menu_with_notifications_description">Shortcuts and notifications
     </string>
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8088f64..2f83f45 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -196,7 +196,6 @@
     private final int[] mTmpAddItemCellCoordinates = new int[2];
 
     @Thunk Hotseat mHotseat;
-    private View mDragHandleIndicator;
     @Nullable private View mHotseatSearchBox;
 
     private DropTargetBar mDropTargetBar;
@@ -933,7 +932,6 @@
         mOverviewPanel = findViewById(R.id.overview_panel);
         mOverviewPanelContainer = findViewById(R.id.overview_panel_container);
         mHotseat = findViewById(R.id.hotseat);
-        mDragHandleIndicator = findViewById(R.id.drag_indicator);
         mHotseatSearchBox = findViewById(R.id.search_container_hotseat);
 
         mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -1194,10 +1192,6 @@
         return mHotseat;
     }
 
-    public View getDragHandleIndicator() {
-        return mDragHandleIndicator;
-    }
-
     public View getHotseatSearchBox() {
         return mHotseatSearchBox;
     }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index f548095..4e10ab6 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
 
+import android.graphics.Rect;
 import android.view.View;
 import android.view.animation.Interpolator;
 
@@ -50,7 +51,6 @@
     public static final int ALL_APPS_HEADER = 1 << 2;
     public static final int ALL_APPS_HEADER_EXTRA = 1 << 3; // e.g. app predictions
     public static final int ALL_APPS_CONTENT = 1 << 4;
-    public static final int DRAG_HANDLE_INDICATOR = 1 << 5;
 
     protected static final int FLAG_MULTI_PAGE = 1 << 0;
     protected static final int FLAG_DISABLE_ACCESSIBILITY = 1 << 1;
@@ -86,6 +86,8 @@
     public static final LauncherState FAST_OVERVIEW = new FastOverviewState(3);
     public static final LauncherState ALL_APPS = new AllAppsState(4);
 
+    protected static final Rect sTempRect = new Rect();
+
     public final int ordinal;
 
     /**
@@ -194,9 +196,9 @@
 
     public int getVisibleElements(Launcher launcher) {
         if (launcher.getDeviceProfile().isVerticalBarLayout()) {
-            return HOTSEAT_ICONS | DRAG_HANDLE_INDICATOR;
+            return HOTSEAT_ICONS;
         }
-        return HOTSEAT_ICONS | DRAG_HANDLE_INDICATOR | HOTSEAT_SEARCH_BOX;
+        return HOTSEAT_ICONS | HOTSEAT_SEARCH_BOX;
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 1b9ac21..cf0c7fc 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -24,6 +24,7 @@
 import android.animation.AnimatorSet;
 import android.os.Handler;
 import android.os.Looper;
+import android.support.annotation.IntDef;
 import android.view.View;
 
 import com.android.launcher3.anim.AnimationSuccessListener;
@@ -33,6 +34,8 @@
 import com.android.launcher3.anim.PropertySetter.AnimatedPropertySetter;
 import com.android.launcher3.uioverrides.UiFactory;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
 /**
@@ -80,6 +83,21 @@
 
     public static final String TAG = "StateManager";
 
+    // We separate the state animations into "atomic" and "non-atomic" components. The atomic
+    // components may be run atomically - that is, all at once, instead of user-controlled. However,
+    // atomic components are not restricted to this purpose; they can be user-controlled alongside
+    // non atomic components as well.
+    @IntDef(flag = true, value = {
+            NON_ATOMIC_COMPONENT,
+            ATOMIC_COMPONENT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AnimationComponents {}
+    public static final int NON_ATOMIC_COMPONENT = 1 << 0;
+    public static final int ATOMIC_COMPONENT = 1 << 1;
+
+    public static final int ANIM_ALL = NON_ATOMIC_COMPONENT | ATOMIC_COMPONENT;
+
     private final AnimationConfig mConfig = new AnimationConfig();
     private final Handler mUiHandler;
     private final Launcher mLauncher;
@@ -238,13 +256,21 @@
      */
     public AnimatorPlaybackController createAnimationToNewWorkspace(
             LauncherState state, long duration) {
-        return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration, null);
+        return createAnimationToNewWorkspace(state, duration, LauncherStateManager.ANIM_ALL);
+    }
+
+    public AnimatorPlaybackController createAnimationToNewWorkspace(
+            LauncherState state, long duration, @AnimationComponents int animComponents) {
+        return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration, null,
+                animComponents);
     }
 
     public AnimatorPlaybackController createAnimationToNewWorkspace(LauncherState state,
-            AnimatorSetBuilder builder, long duration, Runnable onCancelRunnable) {
+            AnimatorSetBuilder builder, long duration, Runnable onCancelRunnable,
+            @AnimationComponents int animComponents) {
         mConfig.reset();
         mConfig.userControlled = true;
+        mConfig.animComponents = animComponents;
         mConfig.duration = duration;
         mConfig.playbackController = AnimatorPlaybackController.wrap(
                 createAnimationToNewWorkspaceInternal(state, builder, null), duration,
@@ -361,6 +387,7 @@
     }
 
     public void setCurrentUserControlledAnimation(AnimatorPlaybackController controller) {
+        clearCurrentAnimation();
         setCurrentAnimation(controller.getTarget());
         mConfig.userControlled = true;
         mConfig.playbackController = controller;
@@ -379,15 +406,10 @@
             }
             if (mConfig.playbackController != null
                     && mConfig.playbackController.getTarget() == childAnim) {
-                if (mConfig.mCurrentAnimation != null) {
-                    mConfig.mCurrentAnimation.removeListener(mConfig);
-                    mConfig.mCurrentAnimation = null;
-                }
-                mConfig.playbackController = null;
+                clearCurrentAnimation();
                 break;
             } else if (mConfig.mCurrentAnimation == childAnim) {
-                mConfig.mCurrentAnimation.removeListener(mConfig);
-                mConfig.mCurrentAnimation = null;
+                clearCurrentAnimation();
                 break;
             }
         }
@@ -399,6 +421,14 @@
         mConfig.setAnimation(anim, null);
     }
 
+    private void clearCurrentAnimation() {
+        if (mConfig.mCurrentAnimation != null) {
+            mConfig.mCurrentAnimation.removeListener(mConfig);
+            mConfig.mCurrentAnimation = null;
+        }
+        mConfig.playbackController = null;
+    }
+
     private class StartAnimRunnable implements Runnable {
 
         private final AnimatorSet mAnim;
@@ -425,6 +455,7 @@
         public long duration;
         public boolean userControlled;
         public AnimatorPlaybackController playbackController;
+        public @AnimationComponents int animComponents = ANIM_ALL;
         private PropertySetter mPropertySetter;
 
         private AnimatorSet mCurrentAnimation;
@@ -436,6 +467,7 @@
         public void reset() {
             duration = 0;
             userControlled = false;
+            animComponents = ANIM_ALL;
             mPropertySetter = null;
             mTargetState = null;
 
@@ -471,6 +503,14 @@
             mTargetState = targetState;
             mCurrentAnimation.addListener(this);
         }
+
+        public boolean playAtomicComponent() {
+            return (animComponents & ATOMIC_COMPONENT) != 0;
+        }
+
+        public boolean playNonAtomicComponent() {
+            return (animComponents & NON_ATOMIC_COMPONENT) != 0;
+        }
     }
 
     public interface StateHandler {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 25d9bb6..d870109 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -82,7 +82,6 @@
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.touch.WorkspaceTouchListener;
-import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 8d3d459..d6b2349 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -18,12 +18,12 @@
 
 import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherState.DRAG_HANDLE_INDICATOR;
 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.HOTSEAT_SEARCH_BOX;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 
 import android.view.View;
+import android.view.animation.Interpolator;
 
 import com.android.launcher3.LauncherState.PageAlphaProvider;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
@@ -48,12 +48,12 @@
     }
 
     public void setState(LauncherState toState) {
-        setWorkspaceProperty(toState, NO_ANIM_PROPERTY_SETTER);
+        setWorkspaceProperty(toState, NO_ANIM_PROPERTY_SETTER, new AnimationConfig());
     }
 
     public void setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder,
             AnimationConfig config) {
-        setWorkspaceProperty(toState, config.getPropertySetter(builder));
+        setWorkspaceProperty(toState, config.getPropertySetter(builder), config);
     }
 
     public float getFinalScale() {
@@ -63,37 +63,45 @@
     /**
      * Starts a transition animation for the workspace.
      */
-    private void setWorkspaceProperty(LauncherState state, PropertySetter propertySetter) {
+    private void setWorkspaceProperty(LauncherState state, PropertySetter propertySetter,
+            AnimationConfig config) {
         float[] scaleAndTranslation = state.getWorkspaceScaleAndTranslation(mLauncher);
         mNewScale = scaleAndTranslation[0];
         PageAlphaProvider pageAlphaProvider = state.getWorkspacePageAlphaProvider(mLauncher);
         final int childCount = mWorkspace.getChildCount();
         for (int i = 0; i < childCount; i++) {
             applyChildState(state, (CellLayout) mWorkspace.getChildAt(i), i, pageAlphaProvider,
-                    propertySetter);
+                    propertySetter, config);
         }
 
-        propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, Interpolators.ZOOM_OUT);
-        propertySetter.setFloat(mWorkspace, View.TRANSLATION_X,
-                scaleAndTranslation[1], Interpolators.ZOOM_OUT);
-        propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
-                scaleAndTranslation[2], Interpolators.ZOOM_OUT);
 
         int elements = state.getVisibleElements(mLauncher);
-        float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
-        propertySetter.setViewAlpha(mLauncher.getHotseat().getLayout(), hotseatIconsAlpha,
-                pageAlphaProvider.interpolator);
-        propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
-                hotseatIconsAlpha, pageAlphaProvider.interpolator);
+        boolean playAtomicComponent = config.playAtomicComponent();
+        if (playAtomicComponent) {
+            propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, Interpolators.ZOOM_OUT);
+            float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
+            propertySetter.setViewAlpha(mLauncher.getHotseat().getLayout(), hotseatIconsAlpha,
+                    pageAlphaProvider.interpolator);
+            propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
+                    hotseatIconsAlpha, pageAlphaProvider.interpolator);
+        }
+
+        if (!config.playNonAtomicComponent()) {
+            // Only the alpha and scale, handled above, are included in the atomic animation.
+            return;
+        }
+
+        Interpolator translationInterpolator = !playAtomicComponent ? Interpolators.LINEAR
+                : Interpolators.ZOOM_OUT;
+        propertySetter.setFloat(mWorkspace, View.TRANSLATION_X,
+                scaleAndTranslation[1], translationInterpolator);
+        propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
+                scaleAndTranslation[2], translationInterpolator);
 
         propertySetter.setViewAlpha(mLauncher.getHotseatSearchBox(),
                 (elements & HOTSEAT_SEARCH_BOX) != 0 ? 1 : 0,
                 pageAlphaProvider.interpolator);
 
-        propertySetter.setViewAlpha(mLauncher.getDragHandleIndicator(),
-                (elements & DRAG_HANDLE_INDICATOR) != 0 ? 1 : 0,
-                pageAlphaProvider.interpolator);
-
         // Set scrim
         propertySetter.setFloat(ViewScrim.get(mWorkspace), ViewScrim.PROGRESS,
                 state.getWorkspaceScrimAlpha(mLauncher), Interpolators.LINEAR);
@@ -101,17 +109,22 @@
 
     public void applyChildState(LauncherState state, CellLayout cl, int childIndex) {
         applyChildState(state, cl, childIndex, state.getWorkspacePageAlphaProvider(mLauncher),
-                NO_ANIM_PROPERTY_SETTER);
+                NO_ANIM_PROPERTY_SETTER, new AnimationConfig());
     }
 
     private void applyChildState(LauncherState state, CellLayout cl, int childIndex,
-            PageAlphaProvider pageAlphaProvider, PropertySetter propertySetter) {
+            PageAlphaProvider pageAlphaProvider, PropertySetter propertySetter,
+            AnimationConfig config) {
         float pageAlpha = pageAlphaProvider.getPageAlpha(childIndex);
         int drawableAlpha = Math.round(pageAlpha * (state.hasWorkspacePageBackground ? 255 : 0));
 
-        propertySetter.setInt(cl.getScrimBackground(),
-                DRAWABLE_ALPHA, drawableAlpha, Interpolators.ZOOM_OUT);
-        propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
-                pageAlpha, pageAlphaProvider.interpolator);
+        if (config.playNonAtomicComponent()) {
+            propertySetter.setInt(cl.getScrimBackground(),
+                    DRAWABLE_ALPHA, drawableAlpha, Interpolators.ZOOM_OUT);
+        }
+        if (config.playAtomicComponent()) {
+            propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
+                    pageAlpha, pageAlphaProvider.interpolator);
+        }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 6d70a08..113571a 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -101,7 +101,6 @@
             mAppsView.setAlpha(1);
             mLauncher.getHotseat().setTranslationY(0);
             mLauncher.getWorkspace().getPageIndicator().setTranslationY(0);
-            mLauncher.getDragHandleIndicator().setTranslationY(0);
         }
     }
 
@@ -125,7 +124,6 @@
         if (!mIsVerticalLayout) {
             mLauncher.getHotseat().setTranslationY(hotseatTranslation);
             mLauncher.getWorkspace().getPageIndicator().setTranslationY(hotseatTranslation);
-            mLauncher.getDragHandleIndicator().setTranslationY(hotseatTranslation);
         }
 
         // Use a light system UI (dark icons) if all apps is behind at least half of the
@@ -168,6 +166,11 @@
             return;
         }
 
+        if (!config.playNonAtomicComponent()) {
+            // There is no atomic component for the all apps transition, so just return early.
+            return;
+        }
+
         Interpolator interpolator = config.userControlled ? LINEAR : FAST_OUT_SLOW_IN;
         ObjectAnimator anim =
                 ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, mProgress, targetProgress);
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index 8f1c8df..e8c5f15 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -156,14 +156,14 @@
         float verticalProgress = OVERVIEW.getVerticalProgress(launcher);
 
         TimeInterpolator pathInterpolator = new PathInterpolator(0.35f, 0, 0.5f, 1);
-        Keyframe keyframe3 = Keyframe.ofFloat(0.423f, verticalProgress - (1 - 0.9438f));
+        Keyframe keyframe3 = Keyframe.ofFloat(0.423f, verticalProgress - (1 - 0.9738f));
         keyframe3.setInterpolator(pathInterpolator);
-        Keyframe keyframe4 = Keyframe.ofFloat(0.654f, verticalProgress);
+        Keyframe keyframe4 = Keyframe.ofFloat(0.754f, verticalProgress);
         keyframe4.setInterpolator(pathInterpolator);
 
         PropertyValuesHolder propertyValuesHolder = PropertyValuesHolder.ofKeyframe("progress",
                 Keyframe.ofFloat(0, verticalProgress),
-                Keyframe.ofFloat(0.346f, verticalProgress), keyframe3, keyframe4,
+                Keyframe.ofFloat(0.246f, verticalProgress), keyframe3, keyframe4,
                 Keyframe.ofFloat(1f, verticalProgress));
         ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(null,
                 new PropertyValuesHolder[]{propertyValuesHolder});
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index b209a2d..dae2dbc 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -32,7 +32,6 @@
 public class AnimatorSetBuilder {
 
     public static final int ANIM_VERTICAL_PROGRESS = 0;
-    public static final int ANIM_OVERVIEW_TRANSLATION = 1;
 
     protected final ArrayList<Animator> mAnims = new ArrayList<>();
 
@@ -56,9 +55,9 @@
         AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
         anim.playTogether(mAnims);
         if (!mOnFinishRunnables.isEmpty()) {
-            anim.addListener(new AnimatorListenerAdapter() {
+            anim.addListener(new AnimationSuccessListener() {
                 @Override
-                public void onAnimationEnd(Animator animation) {
+                public void onAnimationSuccess(Animator animation) {
                     for (Runnable onFinishRunnable : mOnFinishRunnables) {
                         onFinishRunnable.run();
                     }
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 06ddf22..0d388fe 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -38,6 +38,7 @@
 
     public static final Interpolator DEACCEL = new DecelerateInterpolator();
     public static final Interpolator DEACCEL_1_5 = new DecelerateInterpolator(1.5f);
+    public static final Interpolator DEACCEL_1_7 = new DecelerateInterpolator(1.7f);
     public static final Interpolator DEACCEL_2 = new DecelerateInterpolator(2);
     public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f);
     public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f);
@@ -57,8 +58,6 @@
         EXAGGERATED_EASE = new PathInterpolator(exaggeratedEase);
     }
 
-    public static final Interpolator APP_CLOSE_ALPHA = new PathInterpolator(0.4f, 0, 1f, 1f);
-
     public static final Interpolator OVERSHOOT_0 = new OvershootInterpolator(0);
 
     public static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index e1e1f83..1e5f854 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -288,7 +288,7 @@
 
             if (mScaledMaskPath != null) {
                 mBgSpringDrawable.setColorFilter(mBaseFilter);
-                mBgSpringDrawable.setColorFilter(mBaseFilter);
+                mFgSpringDrawable.setColorFilter(mBaseFilter);
                 mBadge.setColorFilter(mBaseFilter);
             }
         } else {
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index bad4976..6e48c36 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -18,16 +18,28 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.LauncherStateManager.ATOMIC_COMPONENT;
+import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
+import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationComponents;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 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.ContainerType;
@@ -41,11 +53,17 @@
         implements TouchController, SwipeDetector.Listener {
 
     private static final String TAG = "ASCTouchController";
-    public static final float RECATCH_REJECTION_FRACTION = .0875f;
 
     // Progress after which the transition is assumed to be a success in case user does not fling
     public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
 
+    /**
+     * Play an atomic recents animation when the progress from NORMAL to OVERVIEW reaches this.
+     */
+    public static final float ATOMIC_OVERVIEW_ANIM_THRESHOLD = 0.5f;
+    private static final long ATOMIC_NORMAL_TO_OVERVIEW_DURATION = 120;
+    private static final long ATOMIC_OVERVIEW_TO_NORMAL_DURATION = 200;
+
     protected final Launcher mLauncher;
     protected final SwipeDetector mDetector;
 
@@ -61,6 +79,18 @@
     // Ratio of transition process [0, 1] to drag displacement (px)
     private float mProgressMultiplier;
     private float mDisplacementShift;
+    private boolean mCanBlockFling;
+    private boolean mBlockFling;
+
+    private AnimatorSet mAtomicAnim;
+    private boolean mPassedOverviewAtomicThreshold;
+    // mAtomicAnim plays the atomic components of the state animations when we pass the threshold.
+    // However, if we reinit to transition to a new state (e.g. OVERVIEW -> ALL_APPS) before the
+    // atomic animation finishes, we only control the non-atomic components so that we don't
+    // interfere with the atomic animation. When the atomic animation ends, we start controlling
+    // the atomic components as well, using this controller.
+    private AnimatorPlaybackController mAtomicComponentsController;
+    private float mAtomicComponentsStartProgress;
 
     public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
         mLauncher = l;
@@ -83,14 +113,8 @@
             boolean ignoreSlopWhenSettling = false;
 
             if (mCurrentAnimation != null) {
-                if (mCurrentAnimation.getProgressFraction() > 1 - RECATCH_REJECTION_FRACTION) {
-                    directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
-                } else if (mCurrentAnimation.getProgressFraction() < RECATCH_REJECTION_FRACTION ) {
-                    directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
-                } else {
-                    directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
-                    ignoreSlopWhenSettling = true;
-                }
+                directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+                ignoreSlopWhenSettling = true;
             } else {
                 directionsToDetectScroll = getSwipeDirection();
                 if (directionsToDetectScroll == 0) {
@@ -138,7 +162,7 @@
     protected abstract LauncherState getTargetState(LauncherState fromState,
             boolean isDragTowardPositive);
 
-    protected abstract float initCurrentAnimation();
+    protected abstract float initCurrentAnimation(@AnimationComponents int animComponents);
 
     /**
      * Returns the container that the touch started from when leaving NORMAL state.
@@ -169,24 +193,55 @@
         mToState = newToState;
 
         mStartProgress = 0;
+        mPassedOverviewAtomicThreshold = false;
         if (mCurrentAnimation != null) {
             mCurrentAnimation.setOnCancelRunnable(null);
         }
-        mProgressMultiplier = initCurrentAnimation();
+        int animComponents = goingBetweenNormalAndOverview(mFromState, mToState)
+                ? NON_ATOMIC_COMPONENT : ANIM_ALL;
+        if (mAtomicAnim != null) {
+            // Control the non-atomic components until the atomic animation finishes, then control
+            // the atomic components as well.
+            animComponents = NON_ATOMIC_COMPONENT;
+            mAtomicAnim.addListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animation) {
+                    cancelAtomicComponentsController();
+                    mAtomicComponentsStartProgress = mCurrentAnimation.getProgressFraction();
+                    long duration = (long) (getShiftRange() * 2);
+                    mAtomicComponentsController = AnimatorPlaybackController.wrap(
+                            createAtomicAnimForState(mToState, duration), duration);
+                    mAtomicComponentsController.dispatchOnStart();
+                }
+            });
+        }
+        if (goingBetweenNormalAndOverview(mFromState, mToState)) {
+            cancelAtomicComponentsController();
+        }
+        mProgressMultiplier = initCurrentAnimation(animComponents);
         mCurrentAnimation.dispatchOnStart();
         return true;
     }
 
+    private boolean goingBetweenNormalAndOverview(LauncherState fromState, LauncherState toState) {
+        return (fromState == NORMAL || fromState == OVERVIEW)
+                && (toState == NORMAL || toState == OVERVIEW)
+                && mPendingAnimation == null;
+    }
+
     @Override
     public void onDragStart(boolean start) {
         if (mCurrentAnimation == null) {
             mFromState = mToState = null;
+            mAtomicComponentsController = null;
             reinitCurrentAnimation(false, mDetector.wasInitialTouchPositive());
             mDisplacementShift = 0;
         } else {
             mCurrentAnimation.pause();
             mStartProgress = mCurrentAnimation.getProgressFraction();
         }
+        mCanBlockFling = mFromState == NORMAL;
+        mBlockFling = false;
     }
 
     @Override
@@ -198,17 +253,70 @@
         if (progress <= 0) {
             if (reinitCurrentAnimation(false, isDragTowardPositive)) {
                 mDisplacementShift = displacement;
+                mBlockFling = mCanBlockFling;
             }
         } else if (progress >= 1) {
             if (reinitCurrentAnimation(true, isDragTowardPositive)) {
                 mDisplacementShift = displacement;
+                mBlockFling = mCanBlockFling;
             }
+        } else if (Math.abs(velocity) < SwipeDetector.RELEASE_VELOCITY_PX_MS) {
+            // We prevent flinging after passing a state, but allow it if the user pauses briefly.
+            mBlockFling = false;
         }
+
         return true;
     }
 
     protected void updateProgress(float fraction) {
         mCurrentAnimation.setPlayFraction(fraction);
+        if (mAtomicComponentsController != null) {
+            mAtomicComponentsController.setPlayFraction(fraction - mAtomicComponentsStartProgress);
+        }
+        maybeUpdateAtomicAnim(mFromState, mToState, fraction);
+    }
+
+    /**
+     * When going between normal and overview states, see if we passed the overview threshold and
+     * play the appropriate atomic animation if so.
+     */
+    private void maybeUpdateAtomicAnim(LauncherState fromState, LauncherState toState,
+            float progress) {
+        if (!goingBetweenNormalAndOverview(fromState, toState)) {
+            return;
+        }
+        float threshold = toState == OVERVIEW ? ATOMIC_OVERVIEW_ANIM_THRESHOLD
+                : 1f - ATOMIC_OVERVIEW_ANIM_THRESHOLD;
+        boolean passedThreshold = progress >= threshold;
+        if (passedThreshold != mPassedOverviewAtomicThreshold) {
+            LauncherState targetState = passedThreshold ? toState : fromState;
+            mPassedOverviewAtomicThreshold = passedThreshold;
+            if (mAtomicAnim != null) {
+                mAtomicAnim.cancel();
+            }
+            long duration = targetState == OVERVIEW ? ATOMIC_NORMAL_TO_OVERVIEW_DURATION
+                    : ATOMIC_OVERVIEW_TO_NORMAL_DURATION;
+            mAtomicAnim = createAtomicAnimForState(targetState, duration);
+            mAtomicAnim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mAtomicAnim = null;
+                }
+            });
+            mAtomicAnim.start();
+            mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
+        }
+    }
+
+    private AnimatorSet createAtomicAnimForState(LauncherState targetState, long duration) {
+        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+        AnimationConfig config = new AnimationConfig();
+        config.animComponents = ATOMIC_COMPONENT;
+        config.duration = duration;
+        for (StateHandler handler : mLauncher.getStateManager().getStateHandlers()) {
+            handler.setStateWithAnimation(targetState, builder, config);
+        }
+        return builder.build();
     }
 
     @Override
@@ -217,6 +325,11 @@
         final LauncherState targetState;
         final float progress = mCurrentAnimation.getProgressFraction();
 
+        boolean blockedFling = fling && mBlockFling;
+        if (blockedFling) {
+            fling = false;
+        }
+
         if (fling) {
             logAction = Touch.FLING;
             targetState =
@@ -231,6 +344,8 @@
         final float endProgress;
         final float startProgress;
         final long duration;
+        // Increase the duration if we prevented the fling, as we are going against a high velocity.
+        final long durationMultiplier = blockedFling && targetState == mFromState ? 6 : 1;
 
         if (targetState == mToState) {
             endProgress = 1;
@@ -241,7 +356,7 @@
                 startProgress = Utilities.boundToRange(
                         progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
                 duration = SwipeDetector.calculateDuration(velocity,
-                        endProgress - Math.max(progress, 0));
+                        endProgress - Math.max(progress, 0)) * durationMultiplier;
             }
         } else {
             // Let the state manager know that the animation didn't go to the target state,
@@ -259,16 +374,65 @@
                 startProgress = Utilities.boundToRange(
                         progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
                 duration = SwipeDetector.calculateDuration(velocity,
-                        Math.min(progress, 1) - endProgress);
+                        Math.min(progress, 1) - endProgress) * durationMultiplier;
             }
         }
 
         mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState, logAction));
         ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
         anim.setFloatValues(startProgress, endProgress);
-        updateSwipeCompleteAnimation(anim, duration, targetState, velocity, fling);
+        maybeUpdateAtomicAnim(mFromState, targetState, targetState == mToState ? 1f : 0f);
+        updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()),
+                targetState, velocity, fling);
         mCurrentAnimation.dispatchOnStart();
         anim.start();
+        if (mAtomicAnim == null) {
+            startAtomicComponentsAnim(endProgress, anim.getDuration());
+        } else {
+            mAtomicAnim.addListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    startAtomicComponentsAnim(endProgress, anim.getDuration());
+                }
+            });
+        }
+    }
+
+    /**
+     * Animates the atomic components from the current progress to the final progress.
+     *
+     * Note that this only applies when we are controlling the atomic components separately from
+     * the non-atomic components, which only happens if we reinit before the atomic animation
+     * finishes.
+     */
+    private void startAtomicComponentsAnim(float toProgress, long duration) {
+        if (mAtomicComponentsController != null) {
+            ValueAnimator atomicAnim = mAtomicComponentsController.getAnimationPlayer();
+            atomicAnim.setFloatValues(mAtomicComponentsController.getProgressFraction(), toProgress);
+            atomicAnim.setDuration(duration);
+            atomicAnim.start();
+            atomicAnim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mAtomicComponentsController = null;
+                }
+            });
+        }
+    }
+
+    private long getRemainingAtomicDuration() {
+        if (mAtomicAnim == null) {
+            return 0;
+        }
+        if (Utilities.ATLEAST_OREO) {
+            return mAtomicAnim.getTotalDuration() - mAtomicAnim.getCurrentPlayTime();
+        } else {
+            long remainingDuration = 0;
+            for (Animator anim : mAtomicAnim.getChildAnimations()) {
+                remainingDuration = Math.max(remainingDuration, anim.getDuration());
+            }
+            return remainingDuration;
+        }
     }
 
     protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
@@ -310,7 +474,15 @@
 
     protected void clearState() {
         mCurrentAnimation = null;
+        cancelAtomicComponentsController();
         mDetector.finishedScrolling();
         mDetector.setDetectableScrollConditions(0, false);
     }
+
+    private void cancelAtomicComponentsController() {
+        if (mAtomicComponentsController != null) {
+            mAtomicComponentsController.getAnimationPlayer().cancel();
+            mAtomicComponentsController = null;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/views/LauncherDragIndicator.java b/src/com/android/launcher3/views/LauncherDragIndicator.java
deleted file mode 100644
index 986e4be..0000000
--- a/src/com/android/launcher3/views/LauncherDragIndicator.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.views;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
-
-public class LauncherDragIndicator extends ImageView implements Insettable, OnClickListener {
-
-    private static final int WALLPAPERS = R.string.wallpaper_button_text;
-    private static final int WIDGETS = R.string.widget_button_text;
-    private static final int SETTINGS = R.string.settings_button_text;
-
-    protected final Launcher mLauncher;
-
-    public LauncherDragIndicator(Context context) {
-        this(context, null);
-    }
-
-    public LauncherDragIndicator(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public LauncherDragIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        mLauncher = Launcher.getLauncher(context);
-        setOnClickListener(this);
-    }
-
-    @Override
-    public void setInsets(Rect insets) {
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
-
-        if (grid.isVerticalBarLayout()) {
-            if (grid.isSeascape()) {
-                lp.leftMargin = grid.hotseatBarSidePaddingPx;
-                lp.rightMargin = insets.right;
-                lp.gravity =  Gravity.RIGHT | Gravity.BOTTOM;
-            } else {
-                lp.leftMargin = insets.left;
-                lp.rightMargin = grid.hotseatBarSidePaddingPx;
-                lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
-            }
-            lp.bottomMargin = grid.workspacePadding.bottom;
-            setImageResource(R.drawable.all_apps_handle_landscape);
-        } else {
-            lp.leftMargin = lp.rightMargin = 0;
-            lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
-            lp.bottomMargin = getPortraitBottomMargin(grid, insets);
-            setImageResource(R.drawable.ic_drag_indicator);
-        }
-
-        lp.width = lp.height = grid.pageIndicatorSizePx;
-        setLayoutParams(lp);
-    }
-
-    protected int getPortraitBottomMargin(DeviceProfile grid, Rect insets) {
-        return grid.hotseatBarSizePx + insets.bottom - grid.pageIndicatorSizePx;
-    }
-
-    @Override
-    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
-        super.onInitializeAccessibilityNodeInfo(info);
-        initCustomActions(info);
-    }
-
-    protected void initCustomActions(AccessibilityNodeInfo info) {
-        Context context = getContext();
-        if (Utilities.isWallpaperAllowed(context)) {
-            info.addAction(new AccessibilityAction(WALLPAPERS, context.getText(WALLPAPERS)));
-        }
-        info.addAction(new AccessibilityAction(WIDGETS, context.getText(WIDGETS)));
-        info.addAction(new AccessibilityAction(SETTINGS, context.getText(SETTINGS)));
-    }
-
-    @Override
-    public boolean performAccessibilityAction(int action, Bundle arguments) {
-        if (action == WALLPAPERS) {
-            return OptionsPopupView.startWallpaperPicker(this);
-        } else if (action == WIDGETS) {
-            return OptionsPopupView.onWidgetsClicked(this);
-        } else if (action == SETTINGS) {
-            return OptionsPopupView.startSettings(this);
-        }
-        return super.performAccessibilityAction(action, arguments);
-    }
-
-    @Override
-    public void onClick(View view) {
-        if (!mLauncher.isInState(ALL_APPS)) {
-            mLauncher.getUserEventDispatcher().logActionOnControl(
-                    Action.Touch.TAP, ControlType.ALL_APPS_BUTTON);
-            mLauncher.getStateManager().goToState(ALL_APPS);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index b49b565..28602f5 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -15,28 +15,63 @@
  */
 package com.android.launcher3.views;
 
+import static android.content.Context.ACCESSIBILITY_SERVICE;
 import static android.support.v4.graphics.ColorUtils.compositeColors;
 import static android.support.v4.graphics.ColorUtils.setAlphaComponent;
 
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
+import android.support.v4.widget.ExploreByTouchHelper;
 import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.LauncherStateManager.StateListener;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.uioverrides.WallpaperColorInfo.OnChangeListener;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.util.Themes;
 
+import java.util.List;
+
 /**
  * Simple scrim which draws a flat color
  */
-public class ScrimView extends View implements Insettable, OnChangeListener {
+public class ScrimView extends View implements Insettable, OnChangeListener,
+        AccessibilityStateChangeListener, StateListener {
 
+    private static final int WALLPAPERS = R.string.wallpaper_button_text;
+    private static final int WIDGETS = R.string.widget_button_text;
+    private static final int SETTINGS = R.string.settings_button_text;
+
+    private final Rect mTempRect = new Rect();
+    private final int[] mTempPos = new int[2];
+
+    protected final Launcher mLauncher;
     private final WallpaperColorInfo mWallpaperColorInfo;
+    private final AccessibilityManager mAM;
     protected final int mEndScrim;
 
     protected float mMaxScrimAlpha;
@@ -48,28 +83,56 @@
     protected int mEndFlatColor;
     protected int mEndFlatColorAlpha;
 
+    protected final int mDragHandleSize;
+    private final Rect mDragHandleBounds;
+    private final AccessibilityHelper mAccessibilityHelper;
+    @Nullable
+    protected Drawable mDragHandle;
+
     public ScrimView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mLauncher = Launcher.getLauncher(context);
         mWallpaperColorInfo = WallpaperColorInfo.getInstance(context);
         mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
 
         mMaxScrimAlpha = 0.7f;
+
+        mDragHandleSize = context.getResources()
+                .getDimensionPixelSize(R.dimen.vertical_drag_handle_size);
+        mDragHandleBounds = new Rect(0, 0, mDragHandleSize, mDragHandleSize);
+
+        mAccessibilityHelper = new AccessibilityHelper();
+        ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper);
+
+        mAM = (AccessibilityManager) context.getSystemService(ACCESSIBILITY_SERVICE);
     }
 
     @Override
-    public void setInsets(Rect insets) { }
+    public void setInsets(Rect insets) {
+        updateDragHandleBounds();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        updateDragHandleBounds();
+    }
 
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mWallpaperColorInfo.addOnChangeListener(this);
         onExtractedColorsChanged(mWallpaperColorInfo);
+
+        mAM.addAccessibilityStateChangeListener(this);
+        onAccessibilityStateChanged(mAM.isEnabled());
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mWallpaperColorInfo.removeOnChangeListener(this);
+        mAM.removeAccessibilityStateChangeListener(this);
     }
 
     @Override
@@ -91,6 +154,7 @@
         if (mProgress != progress) {
             mProgress = progress;
             updateColors();
+            updateDragHandleAlpha();
             invalidate();
         }
     }
@@ -102,10 +166,158 @@
                 mEndFlatColor, Math.round((1 - mProgress) * mEndFlatColorAlpha));
     }
 
+    protected void updateDragHandleAlpha() {
+        if (mDragHandle != null) {
+            mDragHandle.setAlpha(Math.round(255 * Utilities.boundToRange(mProgress, 0, 1)));
+        }
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
         if (mCurrentFlatColor != 0) {
             canvas.drawColor(mCurrentFlatColor);
         }
     }
+
+    protected void updateDragHandleBounds() {
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        final int left;
+        final int width = getMeasuredWidth();
+        final int top = getMeasuredHeight() - mDragHandleSize - grid.getInsets().bottom;
+        final int topMargin;
+
+        if (grid.isVerticalBarLayout()) {
+            topMargin = grid.workspacePadding.bottom;
+            if (grid.isSeascape()) {
+                left = width - grid.getInsets().right - mDragHandleSize;
+            } else {
+                left = mDragHandleSize + grid.getInsets().left;
+            }
+        } else {
+            left = (width - mDragHandleSize) / 2;
+            topMargin = grid.hotseatBarSizePx;
+        }
+        mDragHandleBounds.offsetTo(left, top - topMargin);
+
+        if (mDragHandle != null) {
+            mDragHandle.setBounds(mDragHandleBounds);
+        }
+    }
+
+    @Override
+    public void onAccessibilityStateChanged(boolean enabled) {
+        LauncherStateManager stateManager = mLauncher.getStateManager();
+        stateManager.removeStateListener(this);
+
+        if (enabled) {
+            mDragHandle = mLauncher.getDrawable(R.drawable.drag_handle_indicator);
+            mDragHandle.setBounds(mDragHandleBounds);
+
+            stateManager.addStateListener(this);
+            onStateSetImmediately(mLauncher.getStateManager().getState());
+
+            updateDragHandleAlpha();
+        } else {
+            mDragHandle = null;
+        }
+        invalidate();
+    }
+
+    @Override
+    public boolean dispatchHoverEvent(MotionEvent event) {
+        return mAccessibilityHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event);
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        return mAccessibilityHelper.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
+    }
+
+    @Override
+    public void onFocusChanged(boolean gainFocus, int direction,
+            Rect previouslyFocusedRect) {
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+        mAccessibilityHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+    }
+
+    @Override
+    public void onStateTransitionStart(LauncherState toState) {}
+
+    @Override
+    public void onStateTransitionComplete(LauncherState finalState) {
+        onStateSetImmediately(finalState);
+    }
+
+    @Override
+    public void onStateSetImmediately(LauncherState state) {
+        setImportantForAccessibility(state == ALL_APPS
+                ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+    }
+
+    private class AccessibilityHelper extends ExploreByTouchHelper {
+
+        private static final int DRAG_HANDLE_ID = 1;
+
+        public AccessibilityHelper() {
+            super(ScrimView.this);
+        }
+
+        @Override
+        protected int getVirtualViewAt(float x, float y) {
+            return  mDragHandleBounds.contains((int) x, (int) y)
+                    ? DRAG_HANDLE_ID : INVALID_ID;
+        }
+
+        @Override
+        protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
+            virtualViewIds.add(DRAG_HANDLE_ID);
+        }
+
+        @Override
+        protected void onPopulateNodeForVirtualView(int virtualViewId,
+                AccessibilityNodeInfoCompat node) {
+            node.setContentDescription(getContext().getString(R.string.all_apps_button_label));
+            node.setBoundsInParent(mDragHandleBounds);
+
+            getLocationOnScreen(mTempPos);
+            mTempRect.set(mDragHandleBounds);
+            mTempRect.offset(mTempPos[0], mTempPos[1]);
+            node.setBoundsInScreen(mTempRect);
+
+            node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
+            node.setClickable(true);
+            node.setFocusable(true);
+
+            if (mLauncher.isInState(NORMAL)) {
+                Context context = getContext();
+                if (Utilities.isWallpaperAllowed(context)) {
+                    node.addAction(
+                            new AccessibilityActionCompat(WALLPAPERS, context.getText(WALLPAPERS)));
+                }
+                node.addAction(new AccessibilityActionCompat(WIDGETS, context.getText(WIDGETS)));
+                node.addAction(new AccessibilityActionCompat(SETTINGS, context.getText(SETTINGS)));
+            }
+        }
+
+        @Override
+        protected boolean onPerformActionForVirtualView(
+                int virtualViewId, int action, Bundle arguments) {
+            if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
+                mLauncher.getUserEventDispatcher().logActionOnControl(
+                        Action.Touch.TAP, ControlType.ALL_APPS_BUTTON,
+                        mLauncher.getStateManager().getState().containerType);
+                mLauncher.getStateManager().goToState(ALL_APPS);
+                return true;
+            } else if (action == WALLPAPERS) {
+                return OptionsPopupView.startWallpaperPicker(ScrimView.this);
+            } else if (action == WIDGETS) {
+                return OptionsPopupView.onWidgetsClicked(ScrimView.this);
+            } else if (action == SETTINGS) {
+                return OptionsPopupView.startSettings(ScrimView.this);
+            }
+
+            return false;
+        }
+    }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
index 860be5f..80c2485 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
@@ -8,6 +8,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationComponents;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -62,11 +63,11 @@
     }
 
     @Override
-    protected float initCurrentAnimation() {
+    protected float initCurrentAnimation(@AnimationComponents int animComponents) {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);
         mCurrentAnimation = mLauncher.getStateManager()
-                .createAnimationToNewWorkspace(mToState, maxAccuracy);
+                .createAnimationToNewWorkspace(mToState, maxAccuracy, animComponents);
         float startVerticalShift = mFromState.getVerticalProgress(mLauncher) * range;
         float endVerticalShift = mToState.getVerticalProgress(mLauncher) * range;
         float totalShift = endVerticalShift - startVerticalShift;