Merge "Fix fast scrolling is not "enabled" in work tab" into ub-launcher3-master
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
new file mode 100644
index 0000000..335077a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
@@ -0,0 +1,323 @@
+/*
+ * 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.uioverrides;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.util.TouchController;
+import com.android.quickstep.RecentsView;
+import com.android.quickstep.TaskView;
+
+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.Interpolators.DEACCEL_1_5;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+
+/**
+ * Touch controller for swipe interaction in Overview state
+ */
+public class OverviewSwipeController extends AnimatorListenerAdapter
+        implements TouchController, SwipeDetector.Listener {
+
+    private static final String TAG = "OverviewSwipeController";
+
+    private static final float ALLOWED_FLING_DIRECTION_CHANGE_PROGRESS = 0.1f;
+    private static final int SINGLE_FRAME_MS = 16;
+
+    // Progress after which the transition is assumed to be a success in case user does not fling
+    private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
+
+    private final Launcher mLauncher;
+    private final SwipeDetector mDetector;
+    private final RecentsView mRecentsView;
+    private final int[] mTempCords = new int[2];
+
+    private AnimatorPlaybackController mCurrentAnimation;
+    private boolean mCurrentAnimationIsGoingUp;
+
+    private boolean mNoIntercept;
+    private boolean mSwipeDownEnabled;
+
+    private float mDisplacementShift;
+    private float mProgressMultiplier;
+    private float mEndDisplacement;
+
+    private TaskView mTaskBeingDragged;
+
+    public OverviewSwipeController(Launcher launcher) {
+        mLauncher = launcher;
+        mRecentsView = launcher.getOverviewPanel();
+        mDetector = new SwipeDetector(launcher, this, SwipeDetector.VERTICAL);
+    }
+
+    private boolean canInterceptTouch() {
+        if (mCurrentAnimation != null) {
+            // If we are already animating from a previous state, we can intercept.
+            return true;
+        }
+        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+            return false;
+        }
+        return mLauncher.isInState(OVERVIEW);
+    }
+
+    private boolean isEventOverHotseat(MotionEvent ev) {
+        if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+            return ev.getY() >
+                    mLauncher.getDragLayer().getHeight() * OVERVIEW.getVerticalProgress(mLauncher);
+        } else {
+            return mLauncher.getDragLayer().isEventOverHotseat(ev);
+        }
+    }
+
+    @Override
+    public void onAnimationCancel(Animator animation) {
+        if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
+            Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
+            mDetector.finishedScrolling();
+            mCurrentAnimation = null;
+        }
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mNoIntercept = !canInterceptTouch();
+            if (mNoIntercept) {
+                return false;
+            }
+
+            // Now figure out which direction scroll events the controller will start
+            // calling the callbacks.
+            final int directionsToDetectScroll;
+            boolean ignoreSlopWhenSettling = false;
+
+            if (mCurrentAnimation != null) {
+                directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+                ignoreSlopWhenSettling = true;
+            } else {
+                mTaskBeingDragged = null;
+                mSwipeDownEnabled = true;
+
+                int currentPage = mRecentsView.getCurrentPage();
+                if (currentPage == 0) {
+                    // User is on home tile
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+                } else {
+                    View view = mRecentsView.getChildAt(currentPage);
+                    if (mLauncher.getDragLayer().isEventOverView(view, ev) &&
+                            view instanceof TaskView) {
+                        // The tile can be dragged down to open the task.
+                        mTaskBeingDragged = (TaskView) view;
+                        directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+                    } else if (isEventOverHotseat(ev)) {
+                        // The hotseat is being dragged
+                        directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+                        mSwipeDownEnabled = false;
+                    } else {
+                        mNoIntercept = true;
+                        return false;
+                    }
+                }
+            }
+
+            mDetector.setDetectableScrollConditions(
+                    directionsToDetectScroll, ignoreSlopWhenSettling);
+        }
+
+        if (mNoIntercept) {
+            return false;
+        }
+
+        onControllerTouchEvent(ev);
+        return mDetector.isDraggingOrSettling();
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        return mDetector.onTouchEvent(ev);
+    }
+
+    private void reinitAnimationController(boolean goingUp) {
+        if (!goingUp && !mSwipeDownEnabled) {
+            goingUp = true;
+        }
+        if (mCurrentAnimation != null && mCurrentAnimationIsGoingUp == goingUp) {
+            // No need to init
+            return;
+        }
+        if (mCurrentAnimation != null) {
+            mCurrentAnimation.setPlayFraction(0);
+        }
+        mCurrentAnimationIsGoingUp = goingUp;
+        float range = mLauncher.getAllAppsController().getShiftRange();
+        long maxDuration = (long) (2 * range);
+        DragLayer dl = mLauncher.getDragLayer();
+
+        if (mTaskBeingDragged == null) {
+            // User is either going to all apps or home
+            mCurrentAnimation = mLauncher.getStateManager()
+                    .createAnimationToNewWorkspace(goingUp ? ALL_APPS : NORMAL, maxDuration);
+            if (goingUp) {
+                mEndDisplacement = -range;
+            } else {
+                View ws = mLauncher.getWorkspace();
+                mTempCords[1] = ws.getHeight() - ws.getPaddingBottom();
+                dl.getDescendantCoordRelativeToSelf(ws, mTempCords);
+
+                float distance = mTempCords[1];
+                if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+                    mTempCords[1] = 0;
+                    dl.getDescendantCoordRelativeToSelf(mLauncher.getHotseat(), mTempCords);
+                    distance = mTempCords[1] - distance;
+                } else {
+                    distance = dl.getHeight() - distance;
+                }
+
+                mEndDisplacement = distance;
+            }
+        } else {
+            if (goingUp) {
+                AnimatorSet anim = new AnimatorSet();
+                ObjectAnimator translate = ObjectAnimator.ofFloat(
+                        mTaskBeingDragged, View.TRANSLATION_Y, -mTaskBeingDragged.getBottom());
+                translate.setInterpolator(LINEAR);
+                translate.setDuration(maxDuration);
+                anim.play(translate);
+
+                ObjectAnimator alpha = ObjectAnimator.ofFloat(mTaskBeingDragged, View.ALPHA, 0);
+                alpha.setInterpolator(DEACCEL_1_5);
+                alpha.setDuration(maxDuration);
+                anim.play(alpha);
+                mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
+                mEndDisplacement = -mTaskBeingDragged.getBottom();
+            } else {
+                AnimatorSet anim = new AnimatorSet();
+                // TODO: Setup a zoom animation
+                mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
+
+                mTempCords[1] = mTaskBeingDragged.getHeight();
+                dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
+                mEndDisplacement = dl.getHeight() - mTempCords[1];
+            }
+        }
+
+        mCurrentAnimation.getTarget().addListener(this);
+        mCurrentAnimation.dispatchOnStart();
+        mProgressMultiplier = 1 / mEndDisplacement;
+    }
+
+    @Override
+    public void onDragStart(boolean start) {
+        if (mCurrentAnimation == null) {
+            reinitAnimationController(mDetector.wasInitialTouchPositive());
+            mDisplacementShift = 0;
+        } else {
+            mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier;
+            mCurrentAnimation.pause();
+        }
+    }
+
+    @Override
+    public boolean onDrag(float displacement, float velocity) {
+        float totalDisplacement = displacement + mDisplacementShift;
+        boolean isGoingUp =
+                totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0;
+        if (isGoingUp != mCurrentAnimationIsGoingUp) {
+            reinitAnimationController(isGoingUp);
+        }
+        mCurrentAnimation.setPlayFraction(totalDisplacement * mProgressMultiplier);
+        return true;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        final boolean goingToEnd;
+
+        if (fling) {
+            boolean goingUp = velocity < 0;
+            if (!goingUp && !mSwipeDownEnabled) {
+                goingToEnd = false;
+            } else if (goingUp != mCurrentAnimationIsGoingUp) {
+                // In case the fling is in opposite direction, make sure if is close enough
+                // from the start position
+                if (mCurrentAnimation.getProgressFraction()
+                        >= ALLOWED_FLING_DIRECTION_CHANGE_PROGRESS) {
+                    // Not allowed
+                    goingToEnd = false;
+                } else {
+                    reinitAnimationController(goingUp);
+                    goingToEnd = true;
+                }
+            } else {
+                goingToEnd = true;
+            }
+        } else {
+            goingToEnd = mCurrentAnimation.getProgressFraction() > SUCCESS_TRANSITION_PROGRESS;
+        }
+
+        float progress = mCurrentAnimation.getProgressFraction();
+        long animationDuration = SwipeDetector.calculateDuration(
+                velocity, goingToEnd ? (1 - progress) : progress);
+
+        float nextFrameProgress = Utilities.boundToRange(
+                progress + velocity * SINGLE_FRAME_MS / Math.abs(mEndDisplacement), 0f, 1f);
+
+
+        mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd));
+
+        ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
+        anim.setFloatValues(nextFrameProgress, goingToEnd ? 1f : 0f);
+        anim.setDuration(animationDuration);
+        anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
+        anim.start();
+    }
+
+    private void onCurrentAnimationEnd(boolean wasSuccess) {
+        // TODO: Might be a good time to log something.
+        if (mTaskBeingDragged == null) {
+            LauncherState state = wasSuccess ?
+                    (mCurrentAnimationIsGoingUp ? ALL_APPS : NORMAL) : OVERVIEW;
+            mLauncher.getStateManager().goToState(state, false);
+        } else if (wasSuccess) {
+            if (mCurrentAnimationIsGoingUp) {
+                mRecentsView.onTaskDismissed(mTaskBeingDragged);
+            } else {
+                mTaskBeingDragged.launchTask(false);
+            }
+        }
+        mDetector.finishedScrolling();
+        mTaskBeingDragged = null;
+        mCurrentAnimation = null;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
index 2481c32..fb59946 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
@@ -90,7 +90,6 @@
     private static final int FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE = 1 << 0;
     private static final int FLAG_OVERVIEW_DISABLED_FLING = 1 << 1;
     private static final int FLAG_OVERVIEW_DISABLED_CANCEL_STATE = 1 << 2;
-    private static final int FLAG_RECENTS_PLAN_LOADING = 1 << 3;
     private static final int FLAG_OVERVIEW_DISABLED = 1 << 4;
     private static final int FLAG_DISABLED_TWO_TARGETS = 1 << 5;
     private static final int FLAG_DISABLED_BACK_TARGET = 1 << 6;
@@ -272,16 +271,7 @@
             mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(
                     mToState, mTaggedAnimatorSetBuilder, maxAccuracy);
 
-            if (TouchInteractionService.isConnected()) {
-                // Load recents plan
-                RecentsModel recentsModel = RecentsModel.getInstance(mLauncher);
-                if (recentsModel.getLastLoadPlan() != null) {
-                    onRecentsPlanLoaded(recentsModel.getLastLoadPlan());
-                } else {
-                    mDragPauseDetector.addDisabledFlags(FLAG_RECENTS_PLAN_LOADING);
-                    recentsModel.loadTasks(-1, this::onRecentsPlanLoaded);
-                }
-            } else {
+            if (!TouchInteractionService.isConnected()) {
                 mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED);
             }
 
@@ -302,14 +292,6 @@
         }
     }
 
-    private void onRecentsPlanLoaded(RecentsTaskLoadPlan plan) {
-        RecentsView recentsView = mLauncher.getOverviewPanel();
-        recentsView.update(plan);
-        recentsView.initToPage(0);
-
-        mDragPauseDetector.clearDisabledFlags(FLAG_RECENTS_PLAN_LOADING);
-    }
-
     private float getShiftRange() {
         return mLauncher.getAllAppsController().getShiftRange();
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index bd443aa..e848688 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -30,6 +30,7 @@
 import com.android.launcher3.graphics.BitmapRenderer;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.widget.WidgetsFullSheet;
+import com.android.quickstep.RecentsView;
 import com.android.systemui.shared.recents.view.RecentsTransition;
 
 public class UiFactory {
@@ -41,11 +42,11 @@
             return new TouchController[]{
                     new EdgeSwipeController(launcher),
                     new TwoStepSwipeController(launcher),
-                    new OverviewSwipeUpController(launcher)};
+                    new OverviewSwipeController(launcher)};
         } else {
             return new TouchController[]{
                     new TwoStepSwipeController(launcher),
-                    new OverviewSwipeUpController(launcher)};
+                    new OverviewSwipeController(launcher)};
         }
     }
 
@@ -96,4 +97,9 @@
             return result;
         }
     }
+
+    public static void resetOverview(Launcher launcher) {
+        RecentsView recents = launcher.getOverviewPanel();
+        recents.reset();
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
index ce528e3..168c1fe 100644
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Handler;
@@ -56,14 +57,10 @@
 public class NavBarSwipeInteractionHandler extends InternalStateHandler {
 
     private static final int STATE_LAUNCHER_READY = 1 << 0;
-    private static final int STATE_RECENTS_DELAY_COMPLETE = 1 << 1;
-    private static final int STATE_LOAD_PLAN_READY = 1 << 2;
-    private static final int STATE_RECENTS_FULLY_VISIBLE = 1 << 3;
     private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE = 1 << 4;
     private static final int STATE_SCALED_SNAPSHOT_RECENTS = 1 << 5;
     private static final int STATE_SCALED_SNAPSHOT_APP = 1 << 6;
 
-    private static final long RECENTS_VIEW_VISIBILITY_DELAY = 120;
     private static final long RECENTS_VIEW_VISIBILITY_DURATION = 150;
     private static final long MAX_SWIPE_DURATION = 200;
     private static final long MIN_SWIPE_DURATION = 80;
@@ -90,7 +87,7 @@
     // animated to 1, so allow for a smooth transition.
     private final AnimatedFloat mActivityMultiplier = new AnimatedFloat(this::updateFinalShift);
 
-    private final int mRunningTaskId;
+    private final Task mRunningTask;
     private final Context mContext;
 
     private final MultiStateCallback mStateCallback;
@@ -102,7 +99,6 @@
     private QuickScrubController mQuickScrubController;
     private Hotseat mHotseat;
     private AllAppsScrim mAllAppsScrim;
-    private RecentsTaskLoadPlan mLoadPlan;
 
     private boolean mLauncherReady;
     private boolean mTouchEndHandled;
@@ -114,7 +110,11 @@
 
     NavBarSwipeInteractionHandler(RunningTaskInfo runningTaskInfo, Context context,
             @TouchInteractionService.InteractionType int interactionType) {
-        mRunningTaskId = runningTaskInfo.id;
+        // TODO: We need a better way for this
+        TaskKey taskKey = new TaskKey(runningTaskInfo.id, 0, null, UserHandle.myUserId(), 0);
+        mRunningTask = new Task(taskKey, null, null, "", "", Color.BLACK, Color.BLACK,
+                true, false, false, false, null, 0, null, false);
+
         mContext = context;
         mInteractionType = interactionType;
         WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
@@ -131,11 +131,9 @@
         // Build the state callback
         mStateCallback = new MultiStateCallback();
         mStateCallback.addCallback(STATE_LAUNCHER_READY, this::onLauncherReady);
-        mStateCallback.addCallback(STATE_LOAD_PLAN_READY | STATE_RECENTS_DELAY_COMPLETE,
-                this::setTaskPlanToUi);
         mStateCallback.addCallback(STATE_SCALED_SNAPSHOT_APP, this::resumeLastTask);
-        mStateCallback.addCallback(STATE_RECENTS_FULLY_VISIBLE | STATE_SCALED_SNAPSHOT_RECENTS
-                | STATE_ACTIVITY_MULTIPLIER_COMPLETE,
+        mStateCallback.addCallback(
+                STATE_SCALED_SNAPSHOT_RECENTS | STATE_ACTIVITY_MULTIPLIER_COMPLETE,
                 this::onAnimationToLauncherComplete);
         mStateCallback.addCallback(STATE_LAUNCHER_READY | STATE_SCALED_SNAPSHOT_APP,
                 this::cleanupLauncher);
@@ -145,10 +143,6 @@
         mLauncherReady = true;
         executeFrameUpdate();
 
-        // Wait for some time before loading recents so that the first frame is fast
-        new Handler().postDelayed(() -> mStateCallback.setState(STATE_RECENTS_DELAY_COMPLETE),
-                RECENTS_VIEW_VISIBILITY_DELAY);
-
         long duration = Math.min(MAX_SWIPE_DURATION,
                 Math.max((long) (-mCurrentDisplacement / PIXEL_PER_MS), MIN_SWIPE_DURATION));
         if (mCurrentShift.getCurrentAnimation() != null) {
@@ -189,18 +183,20 @@
 
     @Override
     protected void init(Launcher launcher, boolean alreadyOnHome) {
-        AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
-        launcher.getStateManager().goToState(LauncherState.OVERVIEW, alreadyOnHome);
-
         mLauncher = launcher;
+        mRecentsView = launcher.getOverviewPanel();
+        mRecentsView.showTask(mRunningTask);
+        mStateController = mRecentsView.getStateController();
+        mHotseat = mLauncher.getHotseat();
+        mAllAppsScrim = mLauncher.findViewById(R.id.all_apps_scrim);
+
+        AbstractFloatingView.closeAllOpenViews(mLauncher, alreadyOnHome);
+        mLauncher.getStateManager().goToState(LauncherState.OVERVIEW, alreadyOnHome);
+
         mDragView = new SnapshotDragView(mLauncher, mTaskSnapshot);
         mLauncher.getDragLayer().addView(mDragView);
         mDragView.setPivotX(0);
         mDragView.setPivotY(0);
-        mRecentsView = launcher.getOverviewPanel();
-        mStateController = mRecentsView.getStateController();
-        mHotseat = mLauncher.getHotseat();
-        mAllAppsScrim = mLauncher.findViewById(R.id.all_apps_scrim);
 
         boolean interactionIsQuick
                 = mInteractionType == TouchInteractionService.INTERACTION_QUICK_SCRUB
@@ -220,8 +216,6 @@
             // All-apps search box is visible in vertical bar layout.
             mLauncher.getAppsView().setVisibility(View.GONE);
         }
-        mStateController.setTransitionProgress(1);
-        mStateController.setVisibility(false);
         TraceHelper.partitionSection("TouchInt", "Launcher on new intent");
     }
 
@@ -273,26 +267,6 @@
     }
 
     @UiThread
-    public void setRecentsTaskLoadPlan(RecentsTaskLoadPlan loadPlan) {
-        mLoadPlan = loadPlan;
-        mStateCallback.setState(STATE_LOAD_PLAN_READY);
-    }
-
-    private void setTaskPlanToUi() {
-        mRecentsView.update(mLoadPlan);
-        mRecentsView.initToPage(mStartedQuickScrubFromHome ? 0 : mRecentsView.getFirstTaskIndex());
-        ObjectAnimator anim = mStateController.animateVisibility(true /* isVisible */)
-                .setDuration(RECENTS_VIEW_VISIBILITY_DURATION);
-        anim.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                mStateCallback.setState(STATE_RECENTS_FULLY_VISIBLE);
-            }
-        });
-        anim.start();
-    }
-
-    @UiThread
     public void endTouch(float endVelocity) {
         if (mTouchEndHandled) {
             return;
@@ -339,19 +313,16 @@
 
     @UiThread
     private void resumeLastTask() {
-        TaskKey key = null;
-        if (mLoadPlan != null) {
-            Task task = mLoadPlan.getTaskStack().findTaskWithId(mRunningTaskId);
+        // TODO: We need a better way for this
+        TaskKey key = mRunningTask.key;
+        RecentsTaskLoadPlan loadPlan = RecentsModel.getInstance(mContext).getLastLoadPlan();
+        if (loadPlan != null) {
+            Task task = loadPlan.getTaskStack().findTaskWithId(key.id);
             if (task != null) {
                 key = task.key;
             }
         }
 
-        if (key == null) {
-            // TODO: We need a better way for this
-            key = new TaskKey(mRunningTaskId, 0, null, UserHandle.myUserId(), 0);
-        }
-
         ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
         ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(key, opts, null, null);
     }
@@ -372,7 +343,8 @@
         if (mInteractionType == TouchInteractionService.INTERACTION_QUICK_SWITCH) {
             for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
                 TaskView taskView = (TaskView) mRecentsView.getPageAt(i);
-                if (taskView.getTask().key.id != mRunningTaskId) {
+                // TODO: Match the keys directly
+                if (taskView.getTask().key.id != mRunningTask.key.id) {
                     mRecentsView.snapToPage(i, QUICK_SWITCH_SNAP_DURATION);
                     taskView.postDelayed(() -> {taskView.launchTask(true);},
                             QUICK_SWITCH_SNAP_DURATION);
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 112f156..22658b2 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -15,8 +15,10 @@
  */
 package com.android.quickstep;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
+import android.os.Build;
 import android.os.Looper;
 import android.os.UserHandle;
 
@@ -25,7 +27,9 @@
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
 import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
@@ -33,7 +37,8 @@
 /**
  * Singleton class to load and manage recents model.
  */
-public class RecentsModel {
+@TargetApi(Build.VERSION_CODES.O)
+public class RecentsModel extends TaskStackChangeListener {
 
     // We do not need any synchronization for this variable as its only written on UI thread.
     private static RecentsModel INSTANCE;
@@ -59,6 +64,9 @@
     private final MainThreadExecutor mMainThreadExecutor;
 
     private RecentsTaskLoadPlan mLastLoadPlan;
+    private int mLastLoadPlanId;
+    private int mTaskChangeId;
+
     private RecentsModel(Context context) {
         mContext = context;
 
@@ -69,6 +77,10 @@
         mRecentsTaskLoader.startLoader(mContext);
 
         mMainThreadExecutor = new MainThreadExecutor();
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
+
+        mTaskChangeId = 1;
+        loadTasks(-1, null);
     }
 
     public RecentsTaskLoader getRecentsTaskLoader() {
@@ -80,8 +92,20 @@
      * @param taskId The running task id or -1
      * @param callback The callback to receive the task plan once its complete or null. This is
      *                always called on the UI thread.
+     * @return the request id associated with this call.
      */
-    public void loadTasks(int taskId, Consumer<RecentsTaskLoadPlan> callback) {
+    public int loadTasks(int taskId, Consumer<RecentsTaskLoadPlan> callback) {
+        final int requestId = mTaskChangeId;
+
+        // Fail fast if nothing has changed.
+        if (mLastLoadPlanId == mTaskChangeId) {
+            if (callback != null) {
+                final RecentsTaskLoadPlan plan = mLastLoadPlan;
+                mMainThreadExecutor.execute(() -> callback.accept(plan));
+            }
+            return requestId;
+        }
+
         BackgroundExecutor.get().submit(() -> {
             // Preload the plan
             RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(mContext);
@@ -91,11 +115,23 @@
             // Set the load plan on UI thread
             mMainThreadExecutor.execute(() -> {
                 mLastLoadPlan = loadPlan;
+                mLastLoadPlanId = requestId;
+
                 if (callback != null) {
                     callback.accept(loadPlan);
                 }
             });
         });
+        return requestId;
+    }
+
+    @Override
+    public void onTaskStackChanged() {
+        mTaskChangeId++;
+    }
+
+    public boolean isLoadPlanValid(int resultId) {
+        return mTaskChangeId == resultId;
     }
 
     public RecentsTaskLoadPlan getLastLoadPlan() {
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
index 09003b2..d1bbd23 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -16,8 +16,6 @@
 
 package com.android.quickstep;
 
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
 import android.animation.LayoutTransition;
 import android.content.Context;
 import android.graphics.Rect;
@@ -27,10 +25,8 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.uioverrides.OverviewState;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
@@ -44,6 +40,8 @@
 
 import java.util.ArrayList;
 
+import static com.android.launcher3.LauncherState.NORMAL;
+
 /**
  * A list of recent tasks.
  */
@@ -60,6 +58,9 @@
     private boolean mTaskStackListenerRegistered;
     private LayoutTransition mLayoutTransition;
 
+    /**
+     * TODO: Call reloadIdNeeded in onTaskStackChanged.
+     */
     private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
         public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
@@ -76,6 +77,11 @@
     private RecentsViewStateController mStateController;
     private int mFirstTaskIndex;
 
+    private final RecentsModel mModel;
+    private int mLoadPlanId = -1;
+
+    private Task mFirstTask;
+
     public RecentsView(Context context) {
         this(context, null);
     }
@@ -93,6 +99,7 @@
 
         mLauncher = Launcher.getLauncher(context);
         mQuickScrubController = new QuickScrubController(mLauncher);
+        mModel = RecentsModel.getInstance(context);
 
         mScrollState.isRtl = mIsRtl;
     }
@@ -115,7 +122,6 @@
         Rect padding =
                 getPadding(Launcher.getLauncher(getContext()).getDeviceProfile(), getContext());
         setPadding(padding.left, padding.top, padding.right, padding.bottom);
-
         mFirstTaskIndex = getPageCount();
     }
 
@@ -154,9 +160,8 @@
         updateTaskStackListenerState();
     }
 
-    public void update(RecentsTaskLoadPlan loadPlan) {
-        final RecentsTaskLoader loader = RecentsModel.getInstance(getContext())
-                .getRecentsTaskLoader();
+    private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
+        final RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
         TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
         if (stack == null) {
             removeAllViews();
@@ -166,10 +171,17 @@
         // Ensure there are as many views as there are tasks in the stack (adding and trimming as
         // necessary)
         final LayoutInflater inflater = LayoutInflater.from(getContext());
-        final ArrayList<Task> tasks = stack.getTasks();
+        final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
         setLayoutTransition(null);
-        int requiredChildCount = tasks.size() + mFirstTaskIndex;
 
+        if (mFirstTask != null) {
+            // TODO: Handle this case here once we have a valid implementation for mFirstTask
+            if (tasks.isEmpty() || !keysEquals(tasks.get(tasks.size() - 1), mFirstTask)) {
+                // tasks.add(mFirstTask);
+            }
+        }
+
+        final int requiredChildCount = tasks.size() + mFirstTaskIndex;
         for (int i = getChildCount(); i < requiredChildCount; i++) {
             final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
             addView(taskView);
@@ -190,31 +202,17 @@
         }
     }
 
-    public void initToPage(int pageNo) {
-        setCurrentPage(pageNo);
-        if (getPageAt(mCurrentPage) instanceof TaskView) {
-            ((TaskView) getPageAt(mCurrentPage)).setIconScale(0);
-        }
-    }
-
-    public void launchTaskWithId(int taskId) {
-        for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
-            final TaskView taskView = (TaskView) getChildAt(i);
-            if (taskView.getTask().key.id == taskId) {
-                taskView.launchTask(false /* animate */);
-                return;
-            }
-        }
-    }
-
     private void updateTaskStackListenerState() {
         boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
                 && getWindowVisibility() == VISIBLE;
         if (registerStackListener != mTaskStackListenerRegistered) {
             if (registerStackListener) {
-                ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+                ActivityManagerWrapper.getInstance()
+                        .registerTaskStackListener(mTaskStackListener);
+                reloadIfNeeded();
             } else {
-                ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+                ActivityManagerWrapper.getInstance()
+                        .unregisterTaskStackListener(mTaskStackListener);
             }
             mTaskStackListenerRegistered = registerStackListener;
         }
@@ -302,11 +300,74 @@
     public void onTaskDismissed(TaskView taskView) {
         ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
         removeView(taskView);
-        if (getChildCount() == mFirstTaskIndex) {
-            mLauncher.getStateManager().goToState(LauncherState.NORMAL);
+        if (getTaskCount() == 0) {
+            mLauncher.getStateManager().goToState(NORMAL);
         }
     }
 
+    public void reset() {
+        mFirstTask = null;
+        setCurrentPage(0);
+    }
+
+    public int getTaskCount() {
+        return getChildCount() - mFirstTaskIndex;
+    }
+
+    /**
+     * Reloads the view if anything in recents changed.
+     */
+    public void reloadIfNeeded() {
+        if (!mModel.isLoadPlanValid(mLoadPlanId)) {
+            int taskId = -1;
+            if (mFirstTask != null) {
+                taskId = mFirstTask.key.id;
+            }
+            mLoadPlanId = mModel.loadTasks(taskId, this::applyLoadPlan);
+        }
+    }
+
+    /**
+     * Ensures that the first task in the view represents {@param task} and reloads the view
+     * if needed. This allows the swipe-up gesture to assume that the first tile always
+     * corresponds to the correct task.
+     * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
+     * is called.
+     * Also scrolls the view to this task
+     */
+    public void showTask(Task task) {
+        boolean needsReload = false;
+        boolean inflateFirstChild = true;
+        if (getTaskCount() > 0) {
+            TaskView tv = (TaskView) getChildAt(mFirstTaskIndex);
+            inflateFirstChild = !keysEquals(tv.getTask(), task);
+        }
+        if (inflateFirstChild) {
+            needsReload = true;
+            setLayoutTransition(null);
+            // Add an empty view for now
+            final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
+                    .inflate(R.layout.task, this, false);
+            addView(taskView, mFirstTaskIndex);
+            taskView.bind(task);
+            setLayoutTransition(mLayoutTransition);
+        }
+        if (!needsReload) {
+            needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
+        }
+        if (needsReload) {
+            mLoadPlanId = mModel.loadTasks(task.key.id, this::applyLoadPlan);
+        }
+        mFirstTask = task;
+        setCurrentPage(mFirstTaskIndex);
+        ((TaskView) getPageAt(mCurrentPage)).setIconScale(0);
+    }
+
+    private static boolean keysEquals(Task t1, Task t2) {
+        // TODO: Match the keys directly
+        return t1.key.id == t2.key.id;
+    }
+
     public QuickScrubController getQuickScrubController() {
         return mQuickScrubController;
     }
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
index 94d85ee..3f733ca 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -16,29 +16,17 @@
 
 package com.android.quickstep;
 
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Property;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.touch.SwipeDetector;
 import com.android.quickstep.RecentsView.PageCallbacks;
 import com.android.quickstep.RecentsView.ScrollState;
 import com.android.systemui.shared.recents.model.Task;
@@ -52,11 +40,13 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
+
 /**
  * A task in the Recents view.
  */
-public class TaskView extends FrameLayout implements TaskCallbacks, SwipeDetector.Listener,
-        PageCallbacks {
+public class TaskView extends FrameLayout implements TaskCallbacks, PageCallbacks {
 
     /** Designates how "curvy" the carousel is from 0 to 1, where 0 is a straight line. */
     private static final float CURVE_FACTOR = 0.25f;
@@ -70,30 +60,8 @@
      */
     private static final float MAX_PAGE_SCRIM_ALPHA = 0.8f;
 
-    private static final int SWIPE_DIRECTIONS = SwipeDetector.DIRECTION_POSITIVE;
-
-    /**
-     * The task will appear fully dismissed when the distance swiped
-     * reaches this percentage of the card height.
-     */
-    private static final float SWIPE_DISTANCE_HEIGHT_PERCENTAGE = 0.38f;
-
     private static final long SCALE_ICON_DURATION = 120;
 
-    private static final Property<TaskView, Float> PROPERTY_SWIPE_PROGRESS =
-            new Property<TaskView, Float>(Float.class, "swipe_progress") {
-
-                @Override
-                public Float get(TaskView taskView) {
-                    return taskView.mSwipeProgress;
-                }
-
-                @Override
-                public void set(TaskView taskView, Float progress) {
-                    taskView.setSwipeProgress(progress);
-                }
-            };
-
     private static final Property<TaskView, Float> SCALE_ICON_PROPERTY =
             new Property<TaskView, Float>(Float.TYPE, "scale_icon") {
                 @Override
@@ -110,11 +78,6 @@
     private Task mTask;
     private TaskThumbnailView mSnapshotView;
     private ImageView mIconView;
-    private SwipeDetector mSwipeDetector;
-    private float mSwipeDistance;
-    private float mSwipeProgress;
-    private Interpolator mAlphaInterpolator;
-    private Interpolator mSwipeAnimInterpolator;
     private float mIconScale = 1f;
 
     public TaskView(Context context) {
@@ -130,11 +93,6 @@
         setOnClickListener((view) -> {
             launchTask(true /* animate */);
         });
-
-        mSwipeDetector = new SwipeDetector(getContext(), this, SwipeDetector.VERTICAL);
-        mSwipeDetector.setDetectableScrollConditions(SWIPE_DIRECTIONS, false);
-        mAlphaInterpolator = Interpolators.ACCEL_1_5;
-        mSwipeAnimInterpolator = Interpolators.SCROLL_CUBIC;
     }
 
     @Override
@@ -144,15 +102,6 @@
         mIconView = findViewById(R.id.icon);
     }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-        View p = (View) getParent();
-        mSwipeDistance = (getMeasuredHeight() - p.getPaddingTop() - p.getPaddingBottom())
-                * SWIPE_DISTANCE_HEIGHT_PERCENTAGE;
-    }
-
     /**
      * Updates this task view to the given {@param task}.
      */
@@ -223,80 +172,6 @@
         // Do nothing
     }
 
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        mSwipeDetector.onTouchEvent(ev);
-        return super.onInterceptTouchEvent(ev);
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        mSwipeDetector.onTouchEvent(event);
-        return mSwipeDetector.isDraggingOrSettling() || super.onTouchEvent(event);
-    }
-
-    // Swipe detector methods
-
-    @Override
-    public void onDragStart(boolean start) {
-        getParent().requestDisallowInterceptTouchEvent(true);
-    }
-
-    @Override
-    public boolean onDrag(float displacement, float velocity) {
-        setSwipeProgress(Utilities.boundToRange(displacement / mSwipeDistance,
-                allowsSwipeUp() ? -1 : 0, allowsSwipeDown() ? 1 : 0));
-        return true;
-    }
-
-    /**
-     * Indicates the page is being removed.
-     * @param progress Ranges from -1 (fading upwards) to 1 (fading downwards).
-     */
-    private void setSwipeProgress(float progress) {
-        mSwipeProgress = progress;
-        float translationY = mSwipeProgress * mSwipeDistance;
-        float alpha = 1f - mAlphaInterpolator.getInterpolation(Math.abs(mSwipeProgress));
-        // Only change children to avoid changing our properties while dragging.
-        mIconView.setTranslationY(translationY);
-        mSnapshotView.setTranslationY(translationY);
-        mIconView.setAlpha(alpha);
-        mSnapshotView.setAlpha(alpha);
-    }
-
-    private boolean allowsSwipeUp() {
-        return (SWIPE_DIRECTIONS & SwipeDetector.DIRECTION_POSITIVE) != 0;
-    }
-
-    private boolean allowsSwipeDown() {
-        return (SWIPE_DIRECTIONS & SwipeDetector.DIRECTION_NEGATIVE) != 0;
-    }
-
-    @Override
-    public void onDragEnd(float velocity, boolean fling) {
-        boolean movingAwayFromCenter = velocity < 0 == mSwipeProgress < 0;
-        boolean flingAway = fling && movingAwayFromCenter
-                && (allowsSwipeUp() && velocity < 0 || allowsSwipeDown() && velocity > 0);
-        final boolean shouldRemove = flingAway || (!fling && Math.abs(mSwipeProgress) > 0.5f);
-        float fromProgress = mSwipeProgress;
-        float toProgress = !shouldRemove ? 0f : mSwipeProgress < 0 ? -1f : 1f;
-        ValueAnimator swipeAnimator = ObjectAnimator.ofFloat(this, PROPERTY_SWIPE_PROGRESS,
-                fromProgress, toProgress);
-        swipeAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (shouldRemove) {
-                    ((RecentsView) getParent()).onTaskDismissed(TaskView.this);
-                }
-                mSwipeDetector.finishedScrolling();
-            }
-        });
-        swipeAnimator.setDuration(SwipeDetector.calculateDuration(velocity,
-                Math.abs(toProgress - fromProgress)));
-        swipeAnimator.setInterpolator(mSwipeAnimInterpolator);
-        swipeAnimator.start();
-    }
-
     public void animateIconToScale(float scale) {
         ObjectAnimator.ofFloat(this, SCALE_ICON_PROPERTY, scale)
                 .setDuration(SCALE_ICON_DURATION).start();
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 56f0e89..4cfa1b8 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -161,10 +161,10 @@
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         ResolveInfo info = getPackageManager().resolveActivity(mHomeIntent, 0);
         mLauncher = new ComponentName(getPackageName(), info.activityInfo.name);
-        mHomeIntent.setComponent(mLauncher);
+        // Clear the packageName as system can fail to dedupe it b/64108432
+        mHomeIntent.setComponent(mLauncher).setPackage(null);
 
         mEventQueue = new MotionEventQueue(Choreographer.getInstance(), this::handleMotionEvent);
-        mRecentsModel.loadTasks(-1, null);
         sConnected = true;
     }
 
@@ -307,7 +307,7 @@
         });
 
         // Preload the plan
-        mRecentsModel.loadTasks(mRunningTask.id, handler::setRecentsTaskLoadPlan);
+        mRecentsModel.loadTasks(mRunningTask.id, null);
         mInteractionHandler = handler;
     }
 
diff --git a/res/layout/work_tab_footer.xml b/res/layout/work_tab_footer.xml
index e3416ac..dc0fdd4 100644
--- a/res/layout/work_tab_footer.xml
+++ b/res/layout/work_tab_footer.xml
@@ -73,7 +73,6 @@
         android:lines="1"
         android:minHeight="24dp"
         android:paddingStart="12dp"
-        android:text="@string/managed_by_your_organisation"
         android:textColor="?android:attr/textColorHint"
         android:textSize="13sp"/>
 
diff --git a/res/values/config.xml b/res/values/config.xml
index f48d474..2096200 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -101,6 +101,9 @@
     <!-- Package name of the default wallpaper picker. -->
     <string name="wallpaper_picker_package" translatable="false"></string>
 
+    <!-- Whitelisted package to retrieve packagename for badge. Can be empty. -->
+    <string name="shortcutinfocompat_badgepkg_whitelist" translatable="false"></string>
+
     <!-- View ID to use for QSB widget -->
     <item type="id" name="qsb_widget" />
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d77065c..ee09946 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -324,13 +324,16 @@
     <!-- Label of tab to indicate work apps -->
     <string name="all_apps_work_tab">Work</string>
 
-    <!-- Label of the work mode toggle -->
+    <!-- This string is in the work profile tab when a user has All Apps open on their phone. This is a label for a toggle to turn the work profile on and off. "Work profile" means a separate profile on a user's phone that's specifically for their work apps and managed by their company. "Work" is used as an adjective.-->
     <string name="work_profile_toggle_label">Work profile</string>
-    <!-- Title in bottom user education view in work tab -->
+    <!-- Title of an overlay in All Apps. This overlay is letting a user know about their work profile, which is managed by their employer. "Work apps" are apps in a user's work profile.-->
     <string name="bottom_work_tab_user_education_title">Find work apps here</string>
-    <!-- Body text in bottom user education view in work tab -->
-    <string name="bottom_work_tab_user_education_body">Each work app has an orange badge, which means it\'s kept secure by your organization. Work apps can be moved to your Home Screen for easier access.</string>
-    <!-- Label in work tab to tell users that work profile is managed by their organisation. -->
-    <string name="managed_by_your_organisation">Managed by your organisation</string>
+    <!-- Text in an overlay in All Apps. This overlay is letting a user know about their work profile, which is managed by their employer.-->
+    <string name="bottom_work_tab_user_education_body">Each work app has an orange badge and is kept secure by your organization. Move apps to your Home screen for easier access.</string>
+    <!-- This string is in the work profile tab when a user has All Apps open on their phone. It describes the label of a toggle, "Work profile," as being managed by the user's employer.
+    "Organization" is used to represent a variety of businesses, non-profits, and educational institutions).-->
+    <string name="work_mode_on_label">Managed by your organization</string>
+    <!-- This string appears under a the label of a toggle in the work profile tab on a user's phone. It describes the status of the toggle, "Work profile," when it's turned off. "Work profile" means a separate profile on a user's phone that's speficially for their work apps and is managed by their company.-->
+    <string name="work_mode_off_label">Notifications and apps are off</string>
 
 </resources>
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 7a6a244..a279633 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -462,9 +462,9 @@
                     mInsets.top + availableHeightPx);
         } else {
             // Folders should only appear below the drop target bar and above the hotseat
-            return new Rect(mInsets.left,
+            return new Rect(mInsets.left + edgeMarginPx,
                     mInsets.top + dropTargetBarSizePx + edgeMarginPx,
-                    mInsets.left + availableWidthPx,
+                    mInsets.left + availableWidthPx - edgeMarginPx,
                     mInsets.top + availableHeightPx - hotseatBarSizePx
                             - pageIndicatorSizePx - edgeMarginPx);
         }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 670f579..472a5a9 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.uioverrides.AllAppsState;
 import com.android.launcher3.states.SpringLoadedState;
 import com.android.launcher3.uioverrides.OverviewState;
+import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 import java.util.Arrays;
@@ -54,6 +55,9 @@
 
     private static final LauncherState[] sAllStates = new LauncherState[4];
 
+    /**
+     * TODO: Create a separate class for NORMAL state.
+     */
     public static final LauncherState NORMAL = new LauncherState(0, ContainerType.WORKSPACE,
             0, FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED);
 
@@ -175,6 +179,15 @@
         return NORMAL;
     }
 
+    /**
+     * Called when the start transition ends and the user settles on this particular state.
+     */
+    public void onStateTransitionEnd(Launcher launcher) {
+        if (this == NORMAL) {
+            UiFactory.resetOverview(launcher);
+        }
+    }
+
     protected static void dispatchWindowStateChanged(Launcher launcher) {
         launcher.getWindow().getDecorView().sendAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED);
     }
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 3a660dc..bcb6252 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -270,6 +270,7 @@
             mCurrentStableState = state;
         }
 
+        state.onStateTransitionEnd(mLauncher);
         mLauncher.getWorkspace().setClipChildren(!state.disablePageClipping);
         mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
         mLauncher.finishAutoCancelActionMode();
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 234eb81..769f9ba 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
 import com.android.launcher3.anim.SpringAnimationHandler;
+import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.discovery.AppDiscoveryAppInfo;
 import com.android.launcher3.discovery.AppDiscoveryItemView;
@@ -377,6 +378,11 @@
             case VIEW_TYPE_WORK_TAB_FOOTER:
                 WorkModeSwitch workModeToggle = holder.itemView.findViewById(R.id.work_mode_toggle);
                 workModeToggle.refresh();
+                TextView managedByLabel = holder.itemView.findViewById(R.id.managed_by_label);
+                boolean anyProfileQuietModeEnabled = UserManagerCompat.getInstance(
+                        managedByLabel.getContext()).isAnyProfileQuietModeEnabled();
+                managedByLabel.setText(anyProfileQuietModeEnabled
+                        ? R.string.work_mode_off_label : R.string.work_mode_on_label);
                 break;
         }
         if (mBindViewCallback != null) {
diff --git a/src/com/android/launcher3/allapps/PredictionRowView.java b/src/com/android/launcher3/allapps/PredictionRowView.java
index e834ff4..c19d663 100644
--- a/src/com/android/launcher3/allapps/PredictionRowView.java
+++ b/src/com/android/launcher3/allapps/PredictionRowView.java
@@ -28,9 +28,12 @@
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ComponentKeyMapper;
 import com.android.launcher3.util.Themes;
@@ -40,7 +43,8 @@
 import java.util.HashMap;
 import java.util.List;
 
-public class PredictionRowView extends LinearLayout {
+public class PredictionRowView extends LinearLayout implements
+        UserEventDispatcher.LogContainerProvider {
 
     private static final String TAG = "PredictionRowView";
 
@@ -49,7 +53,7 @@
     // The set of predicted app component names
     private final List<ComponentKeyMapper<AppInfo>> mPredictedAppComponents = new ArrayList<>();
     // The set of predicted apps resolved from the component names and the current set of apps
-    private final List<AppInfo> mPredictedApps = new ArrayList<>();
+    private final ArrayList<AppInfo> mPredictedApps = new ArrayList<>();
     private final Paint mPaint;
     // This adapter is only used to create an identical item w/ same behavior as in the all apps RV
     private AllAppsGridAdapter mAdapter;
@@ -207,4 +211,17 @@
             canvas.drawLine(x1, y, x2, y, mPaint);
         }
     }
+
+    @Override
+    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+            LauncherLogProto.Target targetParent) {
+        for (int i = 0; i < mPredictedApps.size(); i++) {
+            AppInfo appInfo = mPredictedApps.get(i);
+            if (appInfo == info) {
+                targetParent.containerType = LauncherLogProto.ContainerType.PREDICTION;
+                target.predictedRank = i;
+                break;
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index 32c9ce3..e7cf092 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -55,22 +55,9 @@
     }
 
     public void refresh() {
-        setCheckedInternal(!isAnyProfileQuietModeEnabled());
-        setEnabled(true);
-    }
-
-    private boolean isAnyProfileQuietModeEnabled() {
         UserManagerCompat userManager = UserManagerCompat.getInstance(getContext());
-        List<UserHandle> userProfiles = userManager.getUserProfiles();
-        for (UserHandle userProfile : userProfiles) {
-            if (Process.myUserHandle().equals(userProfile)) {
-                continue;
-            }
-            if (userManager.isQuietModeEnabled(userProfile)) {
-                return true;
-            }
-        }
-        return false;
+        setCheckedInternal(!userManager.isAnyProfileQuietModeEnabled());
+        setEnabled(true);
     }
 
     private void trySetQuietModeEnabledToAllProfilesAsync(boolean enabled) {
@@ -91,7 +78,7 @@
                     if (Process.myUserHandle().equals(userProfile)) {
                         continue;
                     }
-                    showConfirm |= !userManager.trySetQuietModeEnabled(enabled, userProfile);
+                    showConfirm |= !userManager.requestQuietModeEnabled(enabled, userProfile);
                 }
                 return showConfirm;
             }
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 6819386..68e9847 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -33,6 +33,12 @@
  */
 public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
 
+    /**
+     * Creates an animation controller for the provided animation.
+     * The actual duration does not matter as the animation is manually controlled. It just
+     * needs to be larger than the total number of pixels so that we don't have jittering due
+     * to float (animation-fraction * total duration) to int conversion.
+     */
     public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
 
         /**
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index 197f798..62055dc 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -63,5 +63,6 @@
     public abstract boolean isUserUnlocked(UserHandle user);
 
     public abstract boolean isDemoUser();
-    public abstract boolean trySetQuietModeEnabled(boolean enableQuietMode, UserHandle user);
+    public abstract boolean requestQuietModeEnabled(boolean enableQuietMode, UserHandle user);
+    public abstract boolean isAnyProfileQuietModeEnabled();
 }
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index e6cc319..e57786d 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -83,7 +83,12 @@
     }
 
     @Override
-    public boolean trySetQuietModeEnabled(boolean enableQuietMode, UserHandle user) {
+    public boolean requestQuietModeEnabled(boolean enableQuietMode, UserHandle user) {
+        return false;
+    }
+
+    @Override
+    public boolean isAnyProfileQuietModeEnabled() {
         return false;
     }
 
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVN.java b/src/com/android/launcher3/compat/UserManagerCompatVN.java
index 50a0217..3733565 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVN.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVN.java
@@ -19,8 +19,11 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Build;
+import android.os.Process;
 import android.os.UserHandle;
 
+import java.util.List;
+
 @TargetApi(Build.VERSION_CODES.N)
 public class UserManagerCompatVN extends UserManagerCompatVM {
 
@@ -37,5 +40,19 @@
     public boolean isUserUnlocked(UserHandle user) {
         return mUserManager.isUserUnlocked(user);
     }
+
+    @Override
+    public boolean isAnyProfileQuietModeEnabled() {
+        List<UserHandle> userProfiles = getUserProfiles();
+        for (UserHandle userProfile : userProfiles) {
+            if (Process.myUserHandle().equals(userProfile)) {
+                continue;
+            }
+            if (isQuietModeEnabled(userProfile)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
 
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVP.java b/src/com/android/launcher3/compat/UserManagerCompatVP.java
index a0bf0ab..2e8a8eb 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVP.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVP.java
@@ -26,29 +26,29 @@
 public class UserManagerCompatVP extends UserManagerCompatVNMr1 {
     private static final String TAG = "UserManagerCompatVP";
 
-    private Method mTrySetQuietModeEnabledMethod;
+    private Method mRequestQuietModeEnabled;
 
     UserManagerCompatVP(Context context) {
         super(context);
         // TODO: Replace it with proper API call once SDK is ready.
         try {
-            mTrySetQuietModeEnabledMethod = UserManager.class.getDeclaredMethod(
-                    "trySetQuietModeEnabled", boolean.class, UserHandle.class);
+            mRequestQuietModeEnabled = UserManager.class.getDeclaredMethod(
+                    "requestQuietModeEnabled", boolean.class, UserHandle.class);
         } catch (NoSuchMethodException e) {
-            Log.e(TAG, "trySetQuietModeEnabled is not available", e);
+            Log.e(TAG, "requestQuietModeEnabled is not available", e);
         }
     }
 
     @Override
-    public boolean trySetQuietModeEnabled(boolean enableQuietMode, UserHandle user) {
-        if (mTrySetQuietModeEnabledMethod == null) {
+    public boolean requestQuietModeEnabled(boolean enableQuietMode, UserHandle user) {
+        if (mRequestQuietModeEnabled == null) {
             return false;
         }
         try {
             return (boolean)
-                    mTrySetQuietModeEnabledMethod.invoke(mUserManager, enableQuietMode, user);
+                    mRequestQuietModeEnabled.invoke(mUserManager, enableQuietMode, user);
         } catch (IllegalAccessException | InvocationTargetException e) {
-            Log.e(TAG, "Failed to invoke mTrySetQuietModeEnabledMethod", e);
+            Log.e(TAG, "Failed to invoke mRequestQuietModeEnabled", e);
         }
         return false;
     }
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 9732261..a59b899 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -369,9 +369,9 @@
                 return new FixedSizeEmptyDrawable(iconSize);
             }
             ShortcutInfoCompat si = (ShortcutInfoCompat) obj;
-            Bitmap badge = LauncherIcons.getShortcutInfoBadge(si, appState.getIconCache())
-                    .iconBitmap;
-
+            LauncherIcons li = LauncherIcons.obtain(appState.getContext());
+            Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).iconBitmap;
+            li.recycle();
             float badgeSize = mLauncher.getResources().getDimension(R.dimen.profile_badge_size);
             float insetFraction = (iconSize - badgeSize) / iconSize;
             return new InsetDrawable(new FastBitmapDrawable(badge),
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 64f96d5..8abafb0 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 
 import android.animation.Animator;
@@ -929,7 +930,12 @@
         int centeredTop = centerY - height / 2;
 
         // We need to bound the folder to the currently visible workspace area
-        mLauncher.getWorkspace().getPageAreaRelativeToDragLayer(sTempRect);
+        if (mLauncher.isInState(OVERVIEW)) {
+            mLauncher.getDragLayer().getDescendantRectRelativeToSelf(mLauncher.getOverviewPanel(),
+                    sTempRect);
+        } else {
+            mLauncher.getWorkspace().getPageAreaRelativeToDragLayer(sTempRect);
+        }
         int left = Math.min(Math.max(sTempRect.left, centeredLeft),
                 sTempRect.right- width);
         int top = Math.min(Math.max(sTempRect.top, centeredTop),
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 0c9f4d9..34fc921 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -356,10 +356,11 @@
         return result;
     }
 
-    public static ItemInfoWithIcon getShortcutInfoBadge(
-            ShortcutInfoCompat shortcutInfo, IconCache cache) {
+    public ItemInfoWithIcon getShortcutInfoBadge(ShortcutInfoCompat shortcutInfo, IconCache cache) {
         ComponentName cn = shortcutInfo.getActivity();
-        if (cn != null) {
+        String badgePkg = shortcutInfo.getBadgePackage(mContext);
+        boolean hasBadgePkgSet = !badgePkg.equals(shortcutInfo.getPackage());
+        if (cn != null && !hasBadgePkgSet) {
             // Get the app info for the source activity.
             AppInfo appInfo = new AppInfo();
             appInfo.user = shortcutInfo.getUserHandle();
@@ -370,7 +371,7 @@
             cache.getTitleAndIcon(appInfo, false);
             return appInfo;
         } else {
-            PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage());
+            PackageItemInfo pkgInfo = new PackageItemInfo(badgePkg);
             cache.getTitleAndIconForApp(pkgInfo, false);
             return pkgInfo;
         }
diff --git a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
index 9c91c87..325777d 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
@@ -18,11 +18,14 @@
 
 import android.annotation.TargetApi;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
 import android.os.Build;
 import android.os.UserHandle;
 
+import com.android.launcher3.R;
+
 /**
  * Wrapper class for {@link android.content.pm.ShortcutInfo}, representing deep shortcuts into apps.
  *
@@ -31,8 +34,8 @@
 @TargetApi(Build.VERSION_CODES.N)
 public class ShortcutInfoCompat {
     private static final String INTENT_CATEGORY = "com.android.launcher3.DEEP_SHORTCUT";
+    private static final String EXTRA_BADGEPKG = "badge_package";
     public static final String EXTRA_SHORTCUT_ID = "shortcut_id";
-
     private ShortcutInfo mShortcutInfo;
 
     public ShortcutInfoCompat(ShortcutInfo shortcutInfo) {
@@ -57,6 +60,15 @@
         return mShortcutInfo.getPackage();
     }
 
+    public String getBadgePackage(Context context) {
+        String whitelistedPkg = context.getString(R.string.shortcutinfocompat_badgepkg_whitelist);
+        if (whitelistedPkg.equals(getPackage())
+                && mShortcutInfo.getExtras().containsKey(EXTRA_BADGEPKG)) {
+            return mShortcutInfo.getExtras().getString(EXTRA_BADGEPKG);
+        }
+        return getPackage();
+    }
+
     public String getId() {
         return mShortcutInfo.getId();
     }
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
index ff5f64c..df34885 100644
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ b/src/com/android/launcher3/touch/SwipeDetector.java
@@ -286,6 +286,16 @@
         }
     }
 
+    /**
+     * Returns if the start drag was towards the positive direction or negative.
+     *
+     * @see #setDetectableScrollConditions(int, boolean)
+     * @see #DIRECTION_BOTH
+     */
+    public boolean wasInitialTouchPositive() {
+        return mSubtractDisplacement < 0;
+    }
+
     private boolean reportDragging() {
         if (mDisplacement != mLastDisplacement) {
             if (DBG) {
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index fc81e80..2ea10c2 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -56,4 +56,6 @@
         renderer.render(new Canvas(result));
         return result;
     }
+
+    public static void resetOverview(Launcher launcher) { }
 }