Merge "Ensuring that previous animation is completed before starting a new state animation" into ub-launcher3-master
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 3b983d2..4013429 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -69,6 +69,7 @@
   EDITTEXT = 7;
   NOTIFICATION = 8;
   TASK = 9;         // Each page of Recents UI (QuickStep)
+  WEB_APP = 10;
 }
 
 // Used to define what type of container a Target would represent.
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index e414fa0..3c5033e 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
index 23add95..3622fc4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
@@ -37,12 +37,16 @@
 
     @Override
     protected int getSwipeDirection(MotionEvent ev) {
-        mFromState = NORMAL;
-        mToState = OVERVIEW;
         return SwipeDetector.DIRECTION_BOTH;
     }
 
     @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        boolean draggingFromNav = mLauncher.getDeviceProfile().isSeascape() != isDragTowardPositive;
+        return draggingFromNav ? OVERVIEW : NORMAL;
+    }
+
+    @Override
     protected float getShiftRange() {
         return mLauncher.getDragLayer().getWidth();
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
index 720b20a..355b88d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
@@ -58,8 +58,9 @@
         }
     }
 
-    protected LauncherState getTargetState() {
-        if (mLauncher.isInState(ALL_APPS)) {
+    @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        if (fromState == ALL_APPS) {
             // Should swipe down go to OVERVIEW instead?
             return TouchInteractionService.isConnected() ?
                     mLauncher.getStateManager().getLastState() : NORMAL;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
index 7d9cce4..0c34b45 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -32,22 +32,26 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.SysuiEventLogger;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
 
 /**
  * Touch controller for handling various state transitions in portrait UI.
  */
 public class PortraitStatesTouchController extends AbstractStateChangeTouchController {
 
-    private static final float TOTAL_DISTANCE_MULTIPLIER = 2f;
+    private static final float TOTAL_DISTANCE_MULTIPLIER = 3f;
     private static final float LINEAR_SCALE_LIMIT = 1 / TOTAL_DISTANCE_MULTIPLIER;
 
-    // Much be greater than LINEAR_SCALE_LIMIT;
+    // Must be greater than LINEAR_SCALE_LIMIT;
     private static final float MAXIMUM_DISTANCE_FACTOR = 0.9f;
 
     // Maximum amount to overshoot.
@@ -129,29 +133,26 @@
             directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
             mStartContainerType = ContainerType.HOTSEAT;
         } else if (mLauncher.isInState(OVERVIEW)) {
-            directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+            directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
             mStartContainerType = ContainerType.TASKSWITCHER;
         } else {
             return 0;
         }
-        mFromState = mLauncher.getStateManager().getState();
-        mToState = getTargetState();
-        if (mFromState == mToState) {
-            return 0;
-        }
         return directionsToDetectScroll;
     }
 
-    protected LauncherState getTargetState() {
-        if (mLauncher.isInState(ALL_APPS)) {
+    @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        if (fromState == ALL_APPS) {
             // Should swipe down go to OVERVIEW instead?
             return TouchInteractionService.isConnected() ?
                     mLauncher.getStateManager().getLastState() : NORMAL;
-        } else if (mLauncher.isInState(OVERVIEW)) {
-            return ALL_APPS;
-        } else {
+        } else if (fromState == OVERVIEW) {
+            return isDragTowardPositive ? ALL_APPS : NORMAL;
+        } else if (isDragTowardPositive) {
             return TouchInteractionService.isConnected() ? OVERVIEW : ALL_APPS;
         }
+        return fromState;
     }
 
     private AnimatorSetBuilder getNormalToOverviewAnimation() {
@@ -194,8 +195,23 @@
             mClampProgressUpdate = -1;
         }
 
-        mCurrentAnimation = mLauncher.getStateManager()
-                .createAnimationToNewWorkspace(mToState, builder, maxAccuracy);
+        if (mPendingAnimation != null) {
+            mPendingAnimation.finish(false);
+            mPendingAnimation = null;
+        }
+
+        RecentsView recentsView = mLauncher.getOverviewPanel();
+        TaskView taskView = (TaskView) recentsView.getChildAt(recentsView.getNextPage());
+        if (recentsView.shouldSwipeDownLaunchApp() && mFromState == OVERVIEW && mToState == NORMAL
+                && taskView != null) {
+            mPendingAnimation = recentsView.createTaskLauncherAnimation(taskView, maxAccuracy);
+            mPendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN);
+
+            mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation.anim, maxAccuracy);
+        } else {
+            mCurrentAnimation = mLauncher.getStateManager()
+                    .createAnimationToNewWorkspace(mToState, builder, maxAccuracy);
+        }
 
         if (totalShift == 0) {
             totalShift = Math.signum(mFromState.ordinal - mToState.ordinal)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index 7b2487a..e73b219 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -34,7 +34,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.PendingAnimation;
+import com.android.launcher3.util.PendingAnimation;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 3cae371..c1590f6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.uioverrides;
 
+import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.Utilities.getPrefs;
 import static com.android.quickstep.OverviewInteractionState.KEY_SWIPE_UP_ENABLED;
@@ -25,7 +26,9 @@
 import android.content.SharedPreferences;
 
 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.StateHandler;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.OverviewInteractionState;
@@ -89,11 +92,15 @@
     }
 
     public static void onLauncherStateOrResumeChanged(Launcher launcher) {
+        LauncherState state = launcher.getStateManager().getState();
+        DeviceProfile profile = launcher.getDeviceProfile();
         WindowManagerWrapper.getInstance().setShelfHeight(
-                launcher.getStateManager().getState() != ALL_APPS &&
-                        launcher.isUserActive() &&
-                        !launcher.getDeviceProfile().isVerticalBarLayout(),
-                launcher.getDeviceProfile().hotseatBarSizePx);
+                state != ALL_APPS && launcher.isUserActive() && !profile.isVerticalBarLayout(),
+                profile.hotseatBarSizePx);
+
+        if (state == NORMAL) {
+            launcher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(false);
+        }
     }
 
     public static void onTrimMemory(Context context, int level) {
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index bcc986d..0ab2df7 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -23,6 +23,7 @@
 import static android.view.MotionEvent.INVALID_POINTER_ID;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
+import static com.android.systemui.shared.system.NavigationBarCompat.QUICK_STEP_DRAG_SLOP_PX;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
@@ -79,8 +80,7 @@
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
     private int mActivePointerId = INVALID_POINTER_ID;
-    private boolean mGestureStarted;
-    private int mTouchSlop;
+    private boolean mPassedInitialSlop;
     private float mStartDisplacement;
     private WindowTransformSwipeHandler mInteractionHandler;
     private int mDisplayRotation;
@@ -121,8 +121,7 @@
                 mActivePointerId = ev.getPointerId(0);
                 mDownPos.set(ev.getX(), ev.getY());
                 mLastPos.set(mDownPos);
-                mTouchSlop = ViewConfiguration.get(this).getScaledPagingTouchSlop();
-                mGestureStarted = false;
+                mPassedInitialSlop = false;
 
                 // Start the window animation on down to give more time for launcher to draw if the
                 // user didn't start the gesture over the back button
@@ -154,11 +153,20 @@
                     break;
                 }
                 mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+                float displacement = getDisplacement(ev);
+                if (!mPassedInitialSlop && Math.abs(displacement) > QUICK_STEP_DRAG_SLOP_PX) {
+                    mPassedInitialSlop = true;
+                    mStartDisplacement = displacement;
 
-                if (mGestureStarted && mInteractionHandler != null) {
+                    // If we deferred starting the window animation on touch down, then
+                    // start tracking now
+                    if (mIsDeferredDownTarget) {
+                        startTouchTrackingForWindowAnimation(ev.getEventTime());
+                    }
+                }
+
+                if (mPassedInitialSlop && mInteractionHandler != null) {
                     // Move
-                    float displacement = getDisplacement(ev.getX(pointerIndex),
-                            ev.getY(pointerIndex));
                     mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
                 }
                 break;
@@ -179,7 +187,6 @@
             return;
         }
         // Notify the handler that the gesture has actually started
-        mGestureStarted = true;
         mInteractionHandler.onGestureStarted();
     }
 
@@ -244,10 +251,12 @@
 
         if (Looper.myLooper() != Looper.getMainLooper()) {
             startActivity.run();
-            try {
-                drawWaitLock.await(LAUNCHER_DRAW_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            } catch (Exception e) {
-                // We have waited long enough for launcher to draw
+            if (!mIsDeferredDownTarget) {
+                try {
+                    drawWaitLock.await(LAUNCHER_DRAW_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+                } catch (Exception e) {
+                    // We have waited long enough for launcher to draw
+                }
             }
         } else {
             // We should almost always get touch-town on background thread. This is an edge case
@@ -261,7 +270,7 @@
      * the animation can still be running.
      */
     private void finishTouchTracking() {
-        if (mGestureStarted && mInteractionHandler != null) {
+        if (mPassedInitialSlop && mInteractionHandler != null) {
             mVelocityTracker.computeCurrentVelocity(1000,
                     ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
 
@@ -276,7 +285,8 @@
 
             // Also clean up in case the system has handled the UP and canceled the animation before
             // we had a chance to start the recents animation. In such a case, we will not receive
-            ActivityManagerWrapper.getInstance().cancelRecentsAnimation();
+            ActivityManagerWrapper.getInstance().cancelRecentsAnimation(
+                    true /* restoreHomeStackPosition */);
         }
         mVelocityTracker.recycle();
         mVelocityTracker = null;
@@ -323,17 +333,12 @@
 
     @Override
     public void onQuickStep(float eventX, float eventY, long eventTime) {
-        float displacement = getDisplacement(eventX, eventY);
-        mStartDisplacement = Math.signum(displacement) * mTouchSlop;
-        if (mIsDeferredDownTarget) {
-            // If we deferred starting the window animation on touch down, then
-            // start tracking now
-            startTouchTrackingForWindowAnimation(eventTime);
-        }
         notifyGestureStarted();
     }
 
-    private float getDisplacement(float eventX, float eventY) {
+    private float getDisplacement(MotionEvent ev) {
+        float eventX = ev.getX();
+        float eventY = ev.getY();
         float displacement = eventY - mDownPos.y;
         if (isNavBarOnRight()) {
             displacement = eventX - mDownPos.x;
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 958feb7..985a364 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -236,6 +236,11 @@
     }
 
     public void onOverviewToggle() {
+        // If currently screen pinning, do not enter overview
+        if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
+            return;
+        }
+
         long time = SystemClock.elapsedRealtime();
         mMainThreadExecutor.execute(() -> {
             long elapsedTime = time - mLastToggleTime;
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
index 4e11220..12f8d52 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -28,7 +28,9 @@
     public RecentsAnimationControllerCompat controller;
     public RemoteAnimationTargetCompat[] targets;
 
-    private boolean mInputConsumerEnabled;
+    private boolean mInputConsumerEnabled = false;
+    private boolean mBehindSystemBars = true;
+    private boolean mSplitScreenMinimized = false;
 
     public synchronized void setController(
             RecentsAnimationControllerCompat controller, RemoteAnimationTargetCompat[] targets) {
@@ -75,4 +77,42 @@
             });
         }
     }
+
+    public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) {
+        if (mBehindSystemBars == behindSystemBars) {
+            return;
+        }
+        mBehindSystemBars = behindSystemBars;
+        BackgroundExecutor.get().submit(() -> {
+            synchronized (this) {
+                TraceHelper.partitionSection("RecentsController",
+                        "Setting behind system bars on " + controller);
+                if (controller != null) {
+                    controller.setAnimationTargetsBehindSystemBars(behindSystemBars);
+                }
+            }
+        });
+    }
+
+    /**
+     * NOTE: As a workaround for conflicting animations (Launcher animating the task leash, and
+     * SystemUI resizing the docked stack, which resizes the task), we currently only set the
+     * minimized mode, and not the inverse.
+     * TODO: Synchronize the minimize animation with the launcher animation
+     */
+    public void setSplitScreenMinimizedForTransaction(boolean minimized) {
+        if (mSplitScreenMinimized || !minimized) {
+            return;
+        }
+        mSplitScreenMinimized = minimized;
+        BackgroundExecutor.get().submit(() -> {
+            synchronized (this) {
+                TraceHelper.partitionSection("RecentsController",
+                        "Setting minimize dock on " + controller);
+                if (controller != null) {
+                    controller.setSplitScreenMinimized(minimized);
+                }
+            }
+        });
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 1ded4dd..b610f4d 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -134,6 +134,8 @@
             if (triggeredFromAltTab) {
                 setupTouchConsumer(HIT_TARGET_NONE);
                 mEventQueue.onOverviewShownFromAltTab();
+            } else {
+                mOverviewCommandHelper.onOverviewShown();
             }
         }
 
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 33f1310..36a9d56 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -497,6 +497,7 @@
                 TransactionCompat transaction = new TransactionCompat();
                 for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
                     if (app.mode == MODE_CLOSING) {
+                        mTmpMatrix.postTranslate(app.position.x, app.position.y);
                         transaction.setMatrix(app.leash, mTmpMatrix)
                                 .setWindowCrop(app.leash, mClipRect);
 
@@ -534,10 +535,10 @@
                     }
                 }
                 if (mRecentsAnimationWrapper.controller != null) {
-
                     // TODO: This logic is spartanic!
-                    mRecentsAnimationWrapper.controller.setAnimationTargetsBehindSystemBars(
-                            shift < 0.12f);
+                    boolean passedThreshold = shift > 0.12f;
+                    mRecentsAnimationWrapper.setAnimationTargetsBehindSystemBars(!passedThreshold);
+                    mRecentsAnimationWrapper.setSplitScreenMinimizedForTransaction(passedThreshold);
                 }
             };
             if (Looper.getMainLooper() == Looper.myLooper()) {
@@ -762,6 +763,8 @@
         // Animate the first icon.
         mRecentsView.setFirstTaskIconScaledDown(false /* isScaledDown */, true /* animate */);
 
+        mRecentsView.setSwipeDownShouldLaunchApp(true);
+
         reset();
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 50a32e6..e7f69b7 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -60,7 +60,7 @@
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.Themes;
-import com.android.quickstep.PendingAnimation;
+import com.android.launcher3.util.PendingAnimation;
 import com.android.quickstep.QuickScrubController;
 import com.android.quickstep.RecentsAnimationInterpolator;
 import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
@@ -139,6 +139,7 @@
     private boolean mOverviewStateEnabled;
     private boolean mTaskStackListenerRegistered;
     private Runnable mNextPageSwitchRunnable;
+    private boolean mSwipeDownShouldLaunchApp;
 
     private PendingAnimation mPendingAnimation;
 
@@ -275,6 +276,9 @@
             mNextPageSwitchRunnable.run();
             mNextPageSwitchRunnable = null;
         }
+        if (getNextPage() > 0) {
+            setSwipeDownShouldLaunchApp(true);
+        }
     }
 
     @Override
@@ -601,6 +605,14 @@
         }
     }
 
+    public void setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp) {
+        mSwipeDownShouldLaunchApp = swipeDownShouldLaunchApp;
+    }
+
+    public boolean shouldSwipeDownLaunchApp() {
+        return mSwipeDownShouldLaunchApp;
+    }
+
     public interface PageCallbacks {
 
         /**
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 4c8d69f..8b41b58 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -54,6 +54,7 @@
 
     private final TaskOverlay mOverlay;
     private final Paint mPaint = new Paint();
+    private final Paint mLockedPaint = new Paint();
 
     private final Matrix mMatrix = new Matrix();
 
@@ -77,6 +78,7 @@
         mFadeLength = getResources().getDimension(R.dimen.task_fade_length);
         mOverlay = TaskOverlayFactory.get(context).createOverlay(this);
         mPaint.setFilterBitmap(true);
+        mLockedPaint.setColor(Color.WHITE);
     }
 
     public void bind() {
@@ -123,14 +125,19 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        canvas.drawRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(),
-                mCornerRadius, mCornerRadius, mPaint);
+        if (mTask == null) {
+            return;
+        }
+        canvas.drawRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius,
+                mCornerRadius, mTask.isLocked ? mLockedPaint : mPaint);
     }
 
     private void updateThumbnailPaintFilter() {
         int mul = (int) (mDimAlpha * 255);
         if (mBitmapShader != null) {
-            mPaint.setColorFilter(getLightingColorFilter(mul));
+            LightingColorFilter filter = getLightingColorFilter(mul);
+            mPaint.setColorFilter(filter);
+            mLockedPaint.setColorFilter(filter);
         } else {
             mPaint.setColorFilter(null);
             mPaint.setColor(Color.argb(255, mul, mul, mul));
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 21f9d5a..fdb6f48 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -18,7 +18,6 @@
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
-
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
 
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 8788db4..ed9873e 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -99,6 +99,7 @@
             mAppsView.setAlpha(1);
             mLauncher.getHotseat().setTranslationY(0);
             mLauncher.getWorkspace().getPageIndicator().setTranslationY(0);
+            mLauncher.getDragHandleIndicator().setTranslationY(0);
         }
     }
 
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 95e1034..278eefd 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -53,6 +53,7 @@
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.WidgetHostViewLoader;
@@ -82,6 +83,7 @@
     private Bundle mWidgetOptions;
 
     private boolean mFinishOnPause = false;
+    private InstantAppResolver mInstantAppResolver;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -95,6 +97,7 @@
 
         mApp = LauncherAppState.getInstance(this);
         mIdp = mApp.getInvariantDeviceProfile();
+        mInstantAppResolver = InstantAppResolver.newInstance(this);
 
         // Use the application context to get the device profile, as in multiwindow-mode, the
         // confirmation activity might be rotated.
@@ -298,7 +301,7 @@
     private void logCommand(int command) {
         getUserEventDispatcher().dispatchUserEvent(newLauncherEvent(
                 newCommandAction(command),
-                newItemTarget(mWidgetCell.getWidgetView()),
+                newItemTarget(mWidgetCell.getWidgetView(), mInstantAppResolver),
                 newContainerTarget(ContainerType.PINITEM)), null);
     }
 }
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index e60a2c7..5094280 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -76,7 +76,7 @@
     /**
      * Draws the {@link #mView} into the given {@param destCanvas}.
      */
-    private void drawDragView(Canvas destCanvas, float scale) {
+    protected void drawDragView(Canvas destCanvas, float scale) {
         destCanvas.save();
         destCanvas.scale(scale, scale);
 
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index d68ac15..01b1424 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -15,10 +15,12 @@
  */
 package com.android.launcher3.logging;
 
+import android.content.Context;
 import android.util.ArrayMap;
 import android.util.SparseArray;
 import android.view.View;
 
+import com.android.launcher3.AppInfo;
 import com.android.launcher3.ButtonDropTarget;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherSettings;
@@ -29,6 +31,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.InstantAppResolver;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
@@ -127,18 +130,21 @@
         return t;
     }
 
-    public static Target newItemTarget(View v) {
+    public static Target newItemTarget(View v, InstantAppResolver instantAppResolver) {
         return (v.getTag() instanceof ItemInfo)
-                ? newItemTarget((ItemInfo) v.getTag())
+                ? newItemTarget((ItemInfo) v.getTag(), instantAppResolver)
                 : newTarget(Target.Type.ITEM);
     }
 
-    public static Target newItemTarget(ItemInfo info) {
+    public static Target newItemTarget(ItemInfo info, InstantAppResolver instantAppResolver) {
         Target t = newTarget(Target.Type.ITEM);
 
         switch (info.itemType) {
             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                t.itemType = ItemType.APP_ICON;
+                t.itemType = (instantAppResolver != null && info instanceof AppInfo
+                        && instantAppResolver.isInstantApp(((AppInfo) info)) )
+                        ? ItemType.WEB_APP
+                        : ItemType.APP_ICON;
                 t.predictedRank = -100; // Never assigned
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 627115d..90355bd 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.LogConfig;
 
 import java.util.Locale;
@@ -78,6 +79,7 @@
         ued.mIsInLandscapeMode = dp.isVerticalBarLayout();
         ued.mIsInMultiWindowMode = dp.isMultiWindowMode;
         ued.mUuidStr = uuidStr;
+        ued.mInstantAppResolver = InstantAppResolver.newInstance(context);
         return ued;
     }
 
@@ -126,6 +128,7 @@
     private boolean mIsInMultiWindowMode;
     private boolean mIsInLandscapeMode;
     private String mUuidStr;
+    protected InstantAppResolver mInstantAppResolver;
 
     //                      APP_ICON    SHORTCUT    WIDGET
     // --------------------------------------------------------------
@@ -151,7 +154,7 @@
 
     public void logAppLaunch(View v, Intent intent) {
         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
-                newItemTarget(v), newTarget(Target.Type.CONTAINER));
+                newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
 
         if (fillInLogContainerData(event, v)) {
             fillIntentInfo(event.srcTarget[0], intent);
@@ -184,7 +187,7 @@
 
     public void logNotificationLaunch(View v, PendingIntent intent) {
         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
-                newItemTarget(v), newTarget(Target.Type.CONTAINER));
+                newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
         if (fillInLogContainerData(event, v)) {
             event.srcTarget[0].packageNameHash = (mUuidStr + intent.getCreatorPackage()).hashCode();
         }
@@ -215,7 +218,7 @@
      */
     public void logActionCommand(int command, View itemView, int srcContainerType) {
         LauncherEvent event = newLauncherEvent(newCommandAction(command),
-                newItemTarget(itemView), newTarget(Target.Type.CONTAINER));
+                newItemTarget(itemView, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
 
         if (fillInLogContainerData(event, itemView)) {
             // TODO: Remove the following two lines once fillInLogContainerData can take in a
@@ -320,7 +323,7 @@
         }
         ItemInfo info = (ItemInfo) icon.getTag();
         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.LONGPRESS),
-                newItemTarget(info), newTarget(Target.Type.CONTAINER));
+                newItemTarget(info, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
         provider.fillInLogContainerData(icon, info, event.srcTarget[0], event.srcTarget[1]);
         dispatchUserEvent(event, null);
 
@@ -338,9 +341,11 @@
 
     public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
-                newItemTarget(dragObj.originalDragInfo), newTarget(Target.Type.CONTAINER));
+                newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
+                newTarget(Target.Type.CONTAINER));
         event.destTarget = new Target[] {
-                newItemTarget(dragObj.originalDragInfo), newDropTarget(dropTargetAsView)
+                newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
+                newDropTarget(dropTargetAsView)
         };
 
         dragObj.dragSource.fillInLogContainerData(null, dragObj.originalDragInfo,
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index a22f450..9726704 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -19,7 +19,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -31,6 +30,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.PendingAnimation;
 
 /**
  * TouchController for handling state changes
@@ -54,10 +54,12 @@
     protected LauncherState mFromState;
     protected LauncherState mToState;
     protected AnimatorPlaybackController mCurrentAnimation;
+    protected PendingAnimation mPendingAnimation;
 
     private float mStartProgress;
     // Ratio of transition process [0, 1] to drag displacement (px)
     private float mProgressMultiplier;
+    private float mDisplacementShift;
 
     public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
         mLauncher = l;
@@ -68,7 +70,7 @@
 
     /**
      * Initializes the {@code mFromState} and {@code mToState} and swipe direction to use for
-     * the detector. In can of disabling swipe, return 0.
+     * the detector. In case of disabling swipe, return 0.
      */
     protected abstract int getSwipeDirection(MotionEvent ev);
 
@@ -122,16 +124,36 @@
         return mLauncher.getAllAppsController().getShiftRange();
     }
 
+    protected abstract LauncherState getTargetState(LauncherState fromState,
+            boolean isDragTowardPositive);
+
     protected abstract float initCurrentAnimation();
 
+    private boolean reinitCurrentAnimation(boolean reachedToState, boolean isDragTowardPositive) {
+        LauncherState newFromState = mFromState == null ? mLauncher.getStateManager().getState()
+                : reachedToState ? mToState : mFromState;
+        LauncherState newToState = getTargetState(newFromState, isDragTowardPositive);
+
+        if (newFromState == mFromState && newToState == mToState || (newFromState == newToState)) {
+            return false;
+        }
+
+        mFromState = newFromState;
+        mToState = newToState;
+
+        mStartProgress = 0;
+        mProgressMultiplier = initCurrentAnimation();
+        mCurrentAnimation.getTarget().addListener(this);
+        mCurrentAnimation.dispatchOnStart();
+        return true;
+    }
+
     @Override
     public void onDragStart(boolean start) {
         if (mCurrentAnimation == null) {
-            mStartProgress = 0;
-            mProgressMultiplier = initCurrentAnimation();
-
-            mCurrentAnimation.getTarget().addListener(this);
-            mCurrentAnimation.dispatchOnStart();
+            mFromState = mToState = null;
+            reinitCurrentAnimation(false, mDetector.wasInitialTouchPositive());
+            mDisplacementShift = 0;
         } else {
             mCurrentAnimation.pause();
             mStartProgress = mCurrentAnimation.getProgressFraction();
@@ -140,8 +162,19 @@
 
     @Override
     public boolean onDrag(float displacement, float velocity) {
-        float deltaProgress = mProgressMultiplier * displacement;
-        updateProgress(deltaProgress + mStartProgress);
+        float deltaProgress = mProgressMultiplier * (displacement - mDisplacementShift);
+        float progress = deltaProgress + mStartProgress;
+        updateProgress(progress);
+        boolean isDragTowardPositive = (displacement - mDisplacementShift) < 0;
+        if (progress <= 0) {
+            if (reinitCurrentAnimation(false, isDragTowardPositive)) {
+                mDisplacementShift = displacement;
+            }
+        } else if (progress >= 1) {
+            if (reinitCurrentAnimation(true, isDragTowardPositive)) {
+                mDisplacementShift = displacement;
+            }
+        }
         return true;
     }
 
@@ -223,7 +256,16 @@
                     mLauncher.getWorkspace().getCurrentPage());
         }
         clearState();
-        mLauncher.getStateManager().goToState(targetState, false /* animated */);
+        boolean shouldGoToTargetState = true;
+        if (mPendingAnimation != null) {
+            boolean reachedTarget = mToState == targetState;
+            mPendingAnimation.finish(reachedTarget);
+            mPendingAnimation = null;
+            shouldGoToTargetState = !reachedTarget;
+        }
+        if (shouldGoToTargetState) {
+            mLauncher.getStateManager().goToState(targetState, false /* animated */);
+        }
     }
 
     protected void clearState() {
diff --git a/quickstep/src/com/android/quickstep/PendingAnimation.java b/src/com/android/launcher3/util/PendingAnimation.java
similarity index 97%
rename from quickstep/src/com/android/quickstep/PendingAnimation.java
rename to src/com/android/launcher3/util/PendingAnimation.java
index d22ef61..4116d56 100644
--- a/quickstep/src/com/android/quickstep/PendingAnimation.java
+++ b/src/com/android/launcher3/util/PendingAnimation.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.quickstep;
+package com.android.launcher3.util;
 
 import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
index e495477..c97c3cc 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
@@ -7,6 +7,7 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -43,12 +44,8 @@
     protected int getSwipeDirection(MotionEvent ev) {
         if (mLauncher.isInState(ALL_APPS)) {
             mStartContainerType = ContainerType.ALLAPPS;
-            mFromState = ALL_APPS;
-            mToState = NORMAL;
             return SwipeDetector.DIRECTION_NEGATIVE;
         } else {
-            mFromState = NORMAL;
-            mToState = ALL_APPS;
             mStartContainerType = mLauncher.getDragLayer().isEventOverHotseat(ev) ?
                     ContainerType.HOTSEAT : ContainerType.WORKSPACE;
             return SwipeDetector.DIRECTION_POSITIVE;
@@ -56,6 +53,11 @@
     }
 
     @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        return fromState == ALL_APPS ? NORMAL : ALL_APPS;
+    }
+
+    @Override
     protected float initCurrentAnimation() {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);