Merge "Remove workspace layout logging that used dumpsys" into ub-launcher3-master
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 1d0b045..04506b5 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -73,6 +73,17 @@
             </intent-filter>
         </provider>
 
+        <!-- FileProvider used for sharing images. -->
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="${packageName}.overview.fileprovider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/overview_file_provider_paths" />
+        </provider>
+
         <service
             android:name="com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL$ColorExtractionService"
             tools:node="remove" />
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index da58817..5b01185 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -26,6 +26,7 @@
 
 import androidx.core.app.NotificationCompat;
 
+import com.android.launcher3.ArrowTipView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.Hotseat;
@@ -42,6 +43,7 @@
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.Snackbar;
 
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -56,7 +58,11 @@
     private static final String NOTIFICATION_CHANNEL_ID = "launcher_onboarding";
     private static final int ONBOARDING_NOTIFICATION_ID = 7641;
 
+    private static final String SETTINGS_ACTION =
+            "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS";
+
     private final Launcher mLauncher;
+    private final Hotseat mHotseat;
     private final NotificationManager mNotificationManager;
     private final Notification mNotification;
     private List<WorkspaceItemInfo> mPredictedApps;
@@ -68,6 +74,7 @@
 
     HotseatEduController(Launcher launcher, Runnable runnable) {
         mLauncher = launcher;
+        mHotseat = launcher.getHotseat();
         mOnOnboardingComplete = runnable;
         mNotificationManager = mLauncher.getSystemService(NotificationManager.class);
         createNotificationChannel();
@@ -98,7 +105,7 @@
 
         //separate folders and items that can get in folders
         for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
-            View view = mLauncher.getHotseat().getChildAt(i, 0);
+            View view = mHotseat.getChildAt(i, 0);
             if (view == null) continue;
             ItemInfo info = (ItemInfo) view.getTag();
             if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
@@ -178,7 +185,6 @@
      */
     private int migrateHotseatWhole() {
         Workspace workspace = mLauncher.getWorkspace();
-        Hotseat hotseatVG = mLauncher.getHotseat();
 
         int pageId = -1;
         int toRow = 0;
@@ -196,7 +202,7 @@
                     .getInt(LauncherSettings.Settings.EXTRA_VALUE);
         }
         for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
-            View child = hotseatVG.getChildAt(i, 0);
+            View child = mHotseat.getChildAt(i, 0);
             if (child == null || child.getTag() == null) continue;
             ItemInfo tag = (ItemInfo) child.getTag();
             mLauncher.getModelWriter().moveItemInDatabase(tag,
@@ -211,8 +217,8 @@
         mNotificationManager.cancel(ONBOARDING_NOTIFICATION_ID);
     }
 
-    void finishOnboarding() {
-        mLauncher.getHotseat().removeAllViewsInLayout();
+    void moveHotseatItems() {
+        mHotseat.removeAllViewsInLayout();
         if (!mNewItems.isEmpty()) {
             int lastPage = mNewItems.get(mNewItems.size() - 1).screenId;
             ArrayList<ItemInfo> animated = new ArrayList<>();
@@ -227,17 +233,34 @@
             }
             mLauncher.bindAppsAdded(mNewScreens, nonAnimated, animated);
         }
+    }
+
+    void finishOnboarding() {
         mOnOnboardingComplete.run();
         destroy();
         mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply();
     }
 
+    void showDimissTip() {
+        if (mHotseat.getShortcutsAndWidgets().getChildCount()
+                < mLauncher.getDeviceProfile().inv.numHotseatIcons) {
+            Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled, R.string.hotseat_turn_off,
+                    null, () -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
+        } else {
+            new ArrowTipView(mLauncher).show(
+                    mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
+        }
+    }
+
     void setPredictedApps(List<WorkspaceItemInfo> predictedApps) {
         mPredictedApps = predictedApps;
         if (!mPredictedApps.isEmpty()
                 && mLauncher.getOrientation() == Configuration.ORIENTATION_PORTRAIT) {
             mNotificationManager.notify(ONBOARDING_NOTIFICATION_ID, mNotification);
         }
+        else {
+            removeNotification();
+        }
     }
 
     private void createNotificationChannel() {
@@ -275,6 +298,17 @@
         }
     }
 
+    void showEdu() {
+        // hotseat is already empty and does not require migration. show edu tip
+        if (mHotseat.getShortcutsAndWidgets().getChildCount() == 0) {
+            new ArrowTipView(mLauncher).show(mLauncher.getString(R.string.hotseat_auto_enrolled),
+                    mHotseat.getTop());
+            finishOnboarding();
+        } else {
+            showDialog();
+        }
+    }
+
     void showDialog() {
         if (mPredictedApps == null || mPredictedApps.isEmpty()) {
             return;
@@ -291,7 +325,7 @@
             ActivityTracker.SchedulerCallback<QuickstepLauncher> {
         @Override
         public boolean init(QuickstepLauncher activity, boolean alreadyOnHome) {
-            activity.getHotseatPredictionController().showEduDialog();
+            activity.getHotseatPredictionController().showEdu();
             return true;
         }
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index bcce168..8944088 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -16,7 +16,8 @@
 package com.android.launcher3.hybridhotseat;
 
 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType
+        .HYBRID_HOTSEAT_CANCELED;
 
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
@@ -27,9 +28,7 @@
 import android.view.View;
 import android.widget.Button;
 import android.widget.TextView;
-import android.widget.Toast;
 
-import com.android.launcher3.ArrowTipView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
@@ -109,18 +108,16 @@
     private void onAccept(View v) {
         mHotseatEduController.migrate();
         handleClose(true);
+
+        mHotseatEduController.moveHotseatItems();
         mHotseatEduController.finishOnboarding();
         //TODO: pass actual page index here.
         // Temporarily we're passing 1 for folder migration and 2 for page migration
         logUserAction(true, FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get() ? 1 : 2);
-        int toastStringRes = !FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()
-                ? R.string.hotseat_items_migrated : R.string.hotseat_items_migrated_alt;
-        Toast.makeText(mLauncher, toastStringRes, Toast.LENGTH_LONG).show();
     }
 
     private void onDismiss(View v) {
-        int top = mLauncher.getHotseat().getTop();
-        new ArrowTipView(mLauncher).show(mLauncher.getString(R.string.hotseat_no_migration), top);
+        mHotseatEduController.showDimissTip();
         mHotseatEduController.finishOnboarding();
         logUserAction(false, -1);
         handleClose(true);
@@ -166,6 +163,7 @@
         target.rank = MIGRATION_EXPERIMENT_IDENTIFIER;
         // encoding migration type on pageIndex
         target.pageIndex = pageIndex;
+        target.cardinality = HotseatPredictionController.MAX_ITEMS_FOR_MIGRATION;
         LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
         UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index d82e9f0..d3bb4f9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -16,6 +16,9 @@
 package com.android.launcher3.hybridhotseat;
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.logging.LoggerUtils.newAction;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -28,6 +31,7 @@
 import android.app.prediction.AppTargetId;
 import android.content.ComponentName;
 import android.os.Bundle;
+import android.provider.DeviceConfig;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -48,6 +52,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.allapps.AllAppsStore;
@@ -58,9 +63,11 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.uioverrides.DeviceFlag;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -84,6 +91,9 @@
     private static final String TAG = "PredictiveHotseat";
     private static final boolean DEBUG = false;
 
+    public static final int MAX_ITEMS_FOR_MIGRATION = DeviceConfig.getInt(
+            DeviceFlag.NAMESPACE_LAUNCHER, "max_homepage_items_for_migration", 5);
+
     //TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543)
     private static final int APPTARGET_ACTION_UNPIN = 4;
 
@@ -112,8 +122,8 @@
 
     private HotseatEduController mHotseatEduController;
 
-    private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
 
+    private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
 
     private final View.OnLongClickListener mPredictionLongClickListener = v -> {
         if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
@@ -146,12 +156,12 @@
     }
 
     /**
-     * Transitions to NORMAL workspace mode and shows edu dialog
+     * Transitions to NORMAL workspace mode and shows edu
      */
-    public void showEduDialog() {
+    public void showEdu() {
         if (mHotseatEduController == null) return;
         mLauncher.getStateManager().goToState(LauncherState.NORMAL, true,
-                () -> mHotseatEduController.showDialog());
+                () -> mHotseatEduController.showEdu());
     }
 
     @Override
@@ -277,7 +287,7 @@
         mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
                 this::setPredictedApps);
         setPauseUIUpdate(false);
-
+        performBetaCheck();
         if (!isReady()) {
             mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor);
         }
@@ -325,7 +335,7 @@
             mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
         }
         predictionLog.append("]");
-        if (false) FileLog.d(TAG, predictionLog.toString());
+        if (Utilities.IS_DEBUG_DEVICE) FileLog.d(TAG, predictionLog.toString());
         updateDependencies();
         if (isReady()) {
             fillGapsWithPrediction();
@@ -589,6 +599,39 @@
         }
     }
 
+    private void performBetaCheck() {
+        if (isReady()) return;
+        int hotseatItemsCount = mHotseat.getShortcutsAndWidgets().getChildCount();
+
+        // -1 to exclude smart space
+        int workspaceItemCount = mLauncher.getWorkspace().getScreenWithId(
+                Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets().getChildCount() - 1;
+
+        // opt user into the feature without onboarding tip or migration if they don't have any
+        // open spots in their hotseat and have more than maxItems in their hotseat + workspace
+
+        if (hotseatItemsCount == mHotSeatItemsCount && workspaceItemCount + hotseatItemsCount
+                > MAX_ITEMS_FOR_MIGRATION) {
+            mLauncher.getSharedPrefs().edit().putBoolean(HotseatEduController.KEY_HOTSEAT_EDU_SEEN,
+                    true).apply();
+
+            LauncherLogProto.Action action = newAction(LauncherLogProto.Action.Type.TOUCH);
+            LauncherLogProto.Target target = newContainerTarget(LauncherLogProto.ContainerType.TIP);
+            action.touch = LauncherLogProto.Action.Touch.TAP;
+            target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
+            target.controlType = LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED;
+
+            // temporarily encode details in log target (go/hotseat_migration)
+            target.rank = 2;
+            target.cardinality = MAX_ITEMS_FOR_MIGRATION;
+            target.pageIndex = (workspaceItemCount * 1000) + hotseatItemsCount;
+            LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
+            UserEventDispatcher.newInstance(mLauncher).dispatchUserEvent(event, null);
+
+
+        }
+    }
+
     /**
      * Fill in predicted_rank field based on app prediction.
      * Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index d1a487a..a6eea0c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -183,8 +183,8 @@
             onStateOrResumeChanged();
         }
 
-        if ((changeBits & ACTIVITY_STATE_STARTED) != 0 && mHotseatPredictionController != null
-                && (getActivityFlags() & ACTIVITY_STATE_USER_ACTIVE) == 0) {
+        if (mHotseatPredictionController != null && ((changeBits & ACTIVITY_STATE_STARTED) != 0
+                || (changeBits & getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
             mHotseatPredictionController.setPauseUIUpdate(false);
         }
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 6fc03b1..8af2747 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -73,7 +73,11 @@
         super(l, false /* allowDragToOverview */);
         mMotionPauseDetector = new MotionPauseDetector(l);
         mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
-        mMotionPauseMaxDisplacement = getShiftRange() * MAX_DISPLACEMENT_PERCENT;
+        mMotionPauseMaxDisplacement = getMotionPauseMaxDisplacement();
+    }
+
+    protected float getMotionPauseMaxDisplacement() {
+        return getShiftRange() * MAX_DISPLACEMENT_PERCENT;
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 064133c..71aa2e8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -66,6 +66,13 @@
     }
 
     @Override
+    protected float getMotionPauseMaxDisplacement() {
+        // No need to disallow pause when swiping up all the way up the screen (unlike
+        // FlingAndHoldTouchController where user is probably intending to go to all apps).
+        return Float.MAX_VALUE;
+    }
+
+    @Override
     protected boolean canInterceptTouch(MotionEvent ev) {
         mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
         return super.canInterceptTouch(ev);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index ce7fa0d..9dce984 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.launcher3.uioverrides.DepthController.DEPTH;
+import static com.android.launcher3.statehandlers.DepthController.DEPTH;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
@@ -34,7 +34,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.uioverrides.DepthController;
+import com.android.launcher3.statehandlers.DepthController;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
 import com.android.quickstep.util.RemoteAnimationProvider;
@@ -105,12 +105,6 @@
             mRecentsView.setRunningTaskIconScaledDown(true);
         }
 
-        DepthController depthController = mActivityInterface.getDepthController();
-        if (depthController != null) {
-            // Update the surface to be the lowest closing app surface
-            depthController.setSurfaceToLauncher(mRecentsView);
-        }
-
         AnimatorSet anim = new AnimatorSet();
         anim.addListener(new AnimationSuccessListener() {
             @Override
@@ -123,11 +117,17 @@
         });
         if (mActivity == null) {
             Log.e(TAG, "Animation created, before activity");
-            anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION))
-                    .with(createDepthAnimator(depthController));
             return anim;
         }
 
+        DepthController depthController = mActivityInterface.getDepthController();
+        if (depthController != null) {
+            anim.play(ObjectAnimator.ofFloat(depthController, DEPTH,
+                    BACKGROUND_APP.getDepth(mActivity),
+                    OVERVIEW.getDepth(mActivity))
+                    .setDuration(RECENTS_LAUNCH_DURATION));
+        }
+
         RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets,
                 wallpaperTargets, MODE_CLOSING);
 
@@ -135,8 +135,6 @@
         RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mTargetTaskId);
         if (runningTaskTarget == null) {
             Log.e(TAG, "No closing app");
-            anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION))
-                    .with(createDepthAnimator(depthController));
             return anim;
         }
 
@@ -183,8 +181,6 @@
                 transaction.apply();
             });
         }
-        anim.play(valueAnimator)
-                .with(createDepthAnimator(depthController));
         return anim;
     }
 
@@ -196,15 +192,4 @@
     long getRecentsLaunchDuration() {
         return RECENTS_LAUNCH_DURATION;
     }
-
-    private Animator createDepthAnimator(DepthController depthController) {
-        if (depthController == null) {
-            // Dummy animation
-            return ValueAnimator.ofInt(0);
-        }
-        return ObjectAnimator.ofFloat(depthController, DEPTH,
-                BACKGROUND_APP.getDepth(mActivity),
-                OVERVIEW.getDepth(mActivity))
-                .setDuration(RECENTS_LAUNCH_DURATION);
-    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
new file mode 100644
index 0000000..33fe5a9
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import static android.content.Intent.EXTRA_STREAM;
+
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.util.ImageActionUtils.persistBitmapAndStartActivity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.BuildConfig;
+import com.android.quickstep.util.ImageActionUtils;
+
+import java.util.function.Supplier;
+
+/**
+ * Contains image selection functions necessary to complete overview action button functions.
+ */
+public class ImageActionsApi {
+
+    private static final String TAG = BuildConfig.APPLICATION_ID + "ImageActionsApi";
+    private final Context mContext;
+    private final Supplier<Bitmap> mBitmapSupplier;
+    private final SystemUiProxy mSystemUiProxy;
+
+    public ImageActionsApi(Context context, Supplier<Bitmap> bitmapSupplier) {
+        mContext = context;
+        mBitmapSupplier = bitmapSupplier;
+        mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
+    }
+
+    /**
+     * Share the image this api was constructed with using the provided intent. The implementation
+     * should add an {@link Intent#EXTRA_STREAM} with the URI pointing to the image to the intent.
+     */
+    @UiThread
+    public void shareWithExplicitIntent(@Nullable Rect crop, Intent intent) {
+        if (mBitmapSupplier.get() == null) {
+            Log.e(TAG, "No snapshot available, not starting share.");
+            return;
+        }
+
+        UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(mContext,
+                mBitmapSupplier.get(), crop, intent, (uri, intentForUri) -> {
+                    intentForUri.putExtra(EXTRA_STREAM, uri);
+                    return new Intent[]{intentForUri};
+                }, TAG));
+
+    }
+
+    /**
+     * Share the image this api was constructed with.
+     */
+    @UiThread
+    public void startShareActivity() {
+        ImageActionUtils.startShareActivity(mContext, mBitmapSupplier, null, null, TAG);
+    }
+
+    /**
+     * @param screenshot       to be saved to the media store.
+     * @param screenshotBounds the location of where the bitmap was laid out on the screen in
+     *                         screen coordinates.
+     * @param visibleInsets    that are used to draw the screenshot within the bounds.
+     * @param taskId           of the task that the screenshot was taken of.
+     */
+    public void saveScreenshot(Bitmap screenshot, Rect screenshotBounds,
+            Insets visibleInsets, int taskId) {
+        ImageActionUtils.saveScreenshot(mSystemUiProxy, screenshot, screenshotBounds, visibleInsets,
+                taskId);
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index 55e6ba2..455ae76 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -55,9 +55,9 @@
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.appprediction.PredictionUiStateManager;
+import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.uioverrides.DepthController;
-import com.android.launcher3.uioverrides.DepthController.ClampedDepthProperty;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.SysUINavigationMode.Mode;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index cafdb62..5bac844 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -294,7 +294,6 @@
         }
 
         setupRecentsViewUi();
-        mActivityInterface.getDepthController().setSurfaceToLauncher(mRecentsView);
 
         if (mDeviceState.getNavMode() == TWO_BUTTONS) {
             // If the device is in two button mode, swiping up will show overview with predictions
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index 33d9d9a..fbf29af 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -16,12 +16,13 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 
+import android.content.Context;
+import android.graphics.Insets;
 import android.graphics.Matrix;
-import android.view.View;
-
-import androidx.annotation.Nullable;
+import android.graphics.Rect;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
@@ -29,6 +30,7 @@
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.TaskThumbnailView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.plugins.OverscrollPlugin;
@@ -43,16 +45,6 @@
  */
 public class TaskOverlayFactory implements ResourceBasedOverride {
 
-    /** Note that these will be shown in order from top to bottom, if available for the task. */
-    private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{
-            TaskShortcutFactory.APP_INFO,
-            TaskShortcutFactory.SPLIT_SCREEN,
-            TaskShortcutFactory.PIN,
-            TaskShortcutFactory.INSTALL,
-            TaskShortcutFactory.FREE_FORM,
-            TaskShortcutFactory.WELLBEING
-    };
-
     public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView) {
         final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
         final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
@@ -76,25 +68,68 @@
     }
 
     public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) {
-        return new TaskOverlay();
+        return new TaskOverlay(thumbnailView);
     }
 
+    /** Note that these will be shown in order from top to bottom, if available for the task. */
+    private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{
+            TaskShortcutFactory.APP_INFO,
+            TaskShortcutFactory.SPLIT_SCREEN,
+            TaskShortcutFactory.PIN,
+            TaskShortcutFactory.INSTALL,
+            TaskShortcutFactory.FREE_FORM,
+            TaskShortcutFactory.WELLBEING
+    };
+
+    /**
+     * Overlay on each task handling Overview Action Buttons.
+     */
     public static class TaskOverlay {
 
+        private final Context mApplicationContext;
+        private OverviewActionsView mActionsView;
+        private final TaskThumbnailView mThumbnailView;
+
+
+        protected TaskOverlay(TaskThumbnailView taskThumbnailView) {
+            mApplicationContext = taskThumbnailView.getContext().getApplicationContext();
+            mThumbnailView = taskThumbnailView;
+        }
+
         /**
          * Called when the current task is interactive for the user
          */
-        public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix) { }
+        public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix) {
+            ImageActionsApi imageApi = new ImageActionsApi(
+                    mApplicationContext, mThumbnailView::getThumbnail);
 
-        @Nullable
-        public View getActionsView() {
-            return null;
+            if (mActionsView == null && ENABLE_OVERVIEW_ACTIONS.get()
+                    && SysUINavigationMode.removeShelfFromOverview(mApplicationContext)) {
+                mActionsView = BaseActivity.fromContext(mThumbnailView.getContext()).findViewById(
+                        R.id.overview_actions_view);
+            }
+            if (mActionsView != null) {
+                mActionsView.setListener(new OverviewActionsView.Listener() {
+                    @Override
+                    public void onShare() {
+                        imageApi.startShareActivity();
+                    }
+
+                    @Override
+                    public void onScreenshot() {
+                        imageApi.saveScreenshot(mThumbnailView.getThumbnail(),
+                                getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key.id);
+                    }
+                });
+            }
+
         }
 
         /**
          * Called when the overlay is no longer used.
          */
-        public void reset() { }
+        public void reset() {
+        }
 
         /**
          * Whether the overlay is modal, which means only tapping is enabled, but no swiping.
@@ -102,5 +137,28 @@
         public boolean isOverlayModal() {
             return false;
         }
+
+        /**
+         * Gets the task snapshot as it is displayed on the screen.
+         *
+         * @return the bounds of the snapshot in screen coordinates.
+         */
+        public Rect getTaskSnapshotBounds() {
+            int[] location = new int[2];
+            mThumbnailView.getLocationOnScreen(location);
+
+            return new Rect(location[0], location[1], mThumbnailView.getWidth() + location[0],
+                    mThumbnailView.getHeight() + location[1]);
+        }
+
+        /**
+         * Gets the insets that the snapshot is drawn with.
+         *
+         * @return the insets in screen coordinates.
+         */
+        public Insets getTaskSnapshotInsets() {
+            // TODO: return the real insets
+            return Insets.of(0, 0, 0, 0);
+        }
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
index 7ec083e..6a3e1fe 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.uioverrides.DepthController.DEPTH;
+import static com.android.launcher3.statehandlers.DepthController.DEPTH;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
 import android.animation.Animator;
@@ -35,7 +35,7 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.uioverrides.DepthController;
+import com.android.launcher3.statehandlers.DepthController;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.views.RecentsView;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index eb5c7f9..496a3d8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.GestureState.DEFAULT_STATE;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
@@ -124,6 +125,7 @@
     private static final String NOTIFY_ACTION_BACK = "com.android.quickstep.action.BACK_GESTURE";
     private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
     private static final int MAX_BACK_NOTIFICATION_COUNT = 3;
+
     private int mBackGestureNotificationCounter = -1;
     @Nullable
     private OverscrollPlugin mOverscrollPlugin;
@@ -263,7 +265,7 @@
     private InputConsumer mConsumer = InputConsumer.NO_OP;
     private Choreographer mMainChoreographer;
     private InputConsumer mResetGestureInputConsumer;
-    private GestureState mGestureState = new GestureState();
+    private GestureState mGestureState = DEFAULT_STATE;
 
     private InputMonitorCompat mInputMonitorCompat;
     private InputEventReceiver mInputEventReceiver;
@@ -435,16 +437,14 @@
 
         Object traceToken = TraceHelper.INSTANCE.beginFlagsOverride(
                 TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
-        mDeviceState.setOrientationTransformIfNeeded(event);
 
         final int action = event.getAction();
         if (action == ACTION_DOWN) {
-            GestureState newGestureState = new GestureState(mOverviewComponentObserver,
-                    ActiveGestureLog.INSTANCE.generateAndSetLogId());
-            newGestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
-                    () -> mAM.getRunningTask(0)));
+            mDeviceState.setOrientationTransformIfNeeded(event);
+            GestureState newGestureState;
 
             if (mDeviceState.isInSwipeUpTouchRegion(event)) {
+                newGestureState = createGestureState();
                 mConsumer.onConsumerAboutToBeSwitched();
                 mConsumer = newConsumer(mGestureState, newGestureState, event);
 
@@ -453,6 +453,7 @@
             } else if (mDeviceState.isUserUnlocked()
                     && mDeviceState.isFullyGesturalNavMode()
                     && mDeviceState.canTriggerAssistantAction(event)) {
+                newGestureState = createGestureState();
                 // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should
                 // not interrupt it. QuickSwitch assumes that interruption can only happen if the
                 // next gesture is also quick switch.
@@ -462,11 +463,18 @@
                     InputConsumer.NO_OP, mInputMonitorCompat,
                     mOverviewComponentObserver.assistantGestureIsConstrained());
             } else {
+                newGestureState = DEFAULT_STATE;
                 mUncheckedConsumer = InputConsumer.NO_OP;
             }
 
             // Save the current gesture state
             mGestureState = newGestureState;
+        } else {
+            // Other events
+            if (mUncheckedConsumer != InputConsumer.NO_OP) {
+                // Only transform the event if we are handling it in a proper consumer
+                mDeviceState.setOrientationTransformIfNeeded(event);
+            }
         }
 
         ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
@@ -481,6 +489,14 @@
         TraceHelper.INSTANCE.endFlagsOverride(traceToken);
     }
 
+    private GestureState createGestureState() {
+        GestureState gestureState = new GestureState(mOverviewComponentObserver,
+                ActiveGestureLog.INSTANCE.generateAndSetLogId());
+        gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
+                () -> mAM.getRunningTask(0)));
+        return gestureState;
+    }
+
     private InputConsumer newConsumer(GestureState previousGestureState,
             GestureState newGestureState, MotionEvent event) {
         boolean canStartSystemGesture = mDeviceState.canStartSystemGesture();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index a87e7eb..71465eb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -41,7 +41,7 @@
 
     protected void setActive(MotionEvent ev) {
         mState = STATE_ACTIVE;
-        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
         mInputMonitor.pilferPointers();
 
         // Send cancel event
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index ba1d38c..7b8d40c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -204,7 +204,7 @@
 
     private void startRecentsTransition() {
         mThresholdCrossed = true;
-        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
         mInputMonitorCompat.pilferPointers();
 
         Intent intent = new Intent(Intent.ACTION_MAIN)
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 416d7a1..a462949 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -314,7 +314,7 @@
         if (mInteractionHandler == null) {
             return;
         }
-        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
         mInputMonitorCompat.pilferPointers();
 
         mActivityInterface.closeOverlay();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index f161cc0..6bfabcd 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -108,7 +108,7 @@
                 ActiveGestureLog.INSTANCE.addLog("startQuickstep");
             }
             if (mInputMonitor != null) {
-                TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
+                TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
                 mInputMonitor.pilferPointers();
             }
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 823b254..ac1c3a8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -65,7 +65,7 @@
 
     private void onInterceptTouch() {
         if (mInputMonitor != null) {
-            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
+            TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
             mInputMonitor.pilferPointers();
         }
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index a027fea..24703bd 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -37,9 +37,9 @@
 import android.view.View;
 import android.widget.FrameLayout;
 
+import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Hotseat;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.StateListener;
 import com.android.launcher3.R;
@@ -47,8 +47,8 @@
 import com.android.launcher3.appprediction.PredictionUiStateManager;
 import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.states.RotationHelper;
-import com.android.launcher3.uioverrides.DepthController;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.views.ScrimView;
@@ -63,7 +63,8 @@
  * {@link RecentsView} used in Launcher activity
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class LauncherRecentsView extends RecentsView<Launcher> implements StateListener {
+public class LauncherRecentsView extends RecentsView<BaseQuickstepLauncher>
+        implements StateListener {
 
     private static final Rect sTempRect = new Rect();
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
new file mode 100644
index 0000000..6a37e2b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+
+/**
+ * View for showing action buttons in Overview
+ */
+public class OverviewActionsView extends FrameLayout {
+
+    private final View mScreenshotButton;
+    private final View mShareButton;
+
+    /**
+     * Listener for taps on the various actions.
+     */
+    public interface Listener {
+        /** User has initiated the share actions. */
+        void onShare();
+
+        /** User has initiated the screenshot action. */
+        void onScreenshot();
+    }
+
+    public OverviewActionsView(Context context) {
+        this(context, null);
+    }
+
+    public OverviewActionsView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public OverviewActionsView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        LayoutInflater.from(context).inflate(R.layout.overview_actions, this, true);
+        mShareButton = findViewById(R.id.action_share);
+        mScreenshotButton = findViewById(R.id.action_screenshot);
+    }
+
+    /**
+     * Set listener for callbacks on action button taps.
+     *
+     * @param listener for callbacks, or {@code null} to clear the listener.
+     */
+    public void setListener(@Nullable OverviewActionsView.Listener listener) {
+        mShareButton.setOnClickListener(
+                listener == null ? null : view -> listener.onShare());
+        mScreenshotButton.setOnClickListener(
+                listener == null ? null : view -> listener.onScreenshot());
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 68c51a0..57a9940 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -30,7 +30,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.uioverrides.DepthController.DEPTH;
+import static com.android.launcher3.statehandlers.DepthController.DEPTH;
 import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
@@ -96,9 +96,9 @@
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
-import com.android.launcher3.uioverrides.DepthController;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
diff --git a/quickstep/res/drawable/ic_screenshot.xml b/quickstep/res/drawable/ic_screenshot.xml
new file mode 100644
index 0000000..d97eae1
--- /dev/null
+++ b/quickstep/res/drawable/ic_screenshot.xml
@@ -0,0 +1,23 @@
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,21L7,21v-1h10v1zM17,18L7,18L7,6h10v12zM17,4L7,4L7,3h10v1zM9.5,8.5L12,8.5L12,7L8,7v4h1.5zM12,17h4v-4h-1.5v2.5L12,15.5z"/>
+</vector>
diff --git a/quickstep/res/drawable/ic_share.xml b/quickstep/res/drawable/ic_share.xml
new file mode 100644
index 0000000..ff4baec
--- /dev/null
+++ b/quickstep/res/drawable/ic_share.xml
@@ -0,0 +1,23 @@
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M18,16c-0.79,0 -1.5,0.31 -2.03,0.81L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.53,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.48 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.05,4.12c-0.05,0.22 -0.09,0.45 -0.09,0.69 0,1.66 1.34,3 3,3s3,-1.34 3,-3 -1.34,-3 -3,-3zM18,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM6,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM18,20c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
+</vector>
diff --git a/quickstep/res/layout/overview_actions.xml b/quickstep/res/layout/overview_actions.xml
new file mode 100644
index 0000000..ad5efb6
--- /dev/null
+++ b/quickstep/res/layout/overview_actions.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <LinearLayout
+        android:id="@+id/action_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:orientation="horizontal">
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1" >
+        </Space>
+        <Button
+            android:id="@+id/action_screenshot"
+            style="@style/OverviewActionButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableTop="@drawable/ic_screenshot"
+            android:text="@string/action_screenshot" />
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1" >
+        </Space>
+
+        <Button
+            android:id="@+id/action_share"
+            style="@style/OverviewActionButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableTop="@drawable/ic_share"
+            android:text="@string/action_share" />
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1" >
+        </Space>
+    </LinearLayout>
+
+</merge>
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
new file mode 100644
index 0000000..328c20b
--- /dev/null
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.quickstep.views.OverviewActionsView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:visibility="gone">
+
+</com.android.quickstep.views.OverviewActionsView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 988c78d..8d42c4a 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -23,7 +23,7 @@
     <dimen name="task_corner_radius_small">2dp</dimen>
 
     <!-- Overrideable in overlay that provides the Overview Actions. -->
-    <dimen name="overview_actions_height">0dp</dimen>
+    <dimen name="overview_actions_height">110dp</dimen>
 
     <dimen name="recents_page_spacing">10dp</dimen>
     <dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index b55b042..40d7c7a 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -78,16 +78,21 @@
     <string name="hotseat_edu_message_migrate">Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your Home screen. </string>
     <string name="hotseat_edu_message_migrate_alt">Easily access your most-used apps, right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move to a new folder.</string>
 
-    <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
-    <string name="hotseat_items_migrated">Your hotseat items have been moved up to your homescreen</string>
-    <string name="hotseat_items_migrated_alt">Your hotseat items have been moved to a folder</string>
-    <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
-    <string name="hotseat_no_migration">Drag apps off the bottom row to see app suggestions</string>
     <!-- Button text to opt in for fully predicted hotseat -->
     <string name="hotseat_edu_accept">Get app suggestions</string>
     <!-- Button text to dismiss opt in for fully predicted hotseat -->
     <string name="hotseat_edu_dismiss">No thanks</string>
 
+    <!-- action shown to turn off predictions after onboarding -->
+    <string name="hotseat_turn_off">Settings</string>
+
+    <!-- tip shown if user has no items in hotseat to migrate -->
+    <string name="hotseat_auto_enrolled">Most-used apps appear here, and change based on routines</string>
+    <!-- tip shown if user declines migration and has no open spots for prediction -->
+    <string name="hotseat_tip_no_empty_slots">Drag apps off the bottom row to get app suggestions</string>
+    <!-- tip shown if user declines migration and has some open spots for prediction -->
+    <string name="hotseat_tip_gaps_filled">App suggestions added to empty space</string>
+
 
     <!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
     <string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>
@@ -108,4 +113,10 @@
     <string name="back_gesture_tutorial_action_button_label" translatable="false">Done</string>
     <!-- Button text shown on a text button on the confirm screen. [CHAR LIMIT=14] -->
     <string name="back_gesture_tutorial_action_text_button_label" translatable="false">Settings</string>
+
+    <!-- ******* Overview ******* -->
+    <!-- Label for a button that causes the current overview app to be shared. [CHAR_LIMIT=40] -->
+    <string translatable="false" name="action_share">Share</string>
+    <!-- Label for a button that causes a screen shot of the current app to be taken. [CHAR_LIMIT=40] -->
+    <string translatable="false" name="action_screenshot">Screenshot</string>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index c8d7777..bf107fb 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -60,4 +60,13 @@
         parent="TextAppearance.BackGestureTutorial.ButtonLabel">
         <item name="android:textColor">@color/back_gesture_tutorial_primary_color</item>
     </style>
+
+    <style name="OverviewActionButton"
+        parent="@android:style/Widget.DeviceDefault.Button.Borderless">
+        <item name="android:textColor">?attr/workspaceTextColor</item>
+        <item name="android:drawableTint">?attr/workspaceTextColor</item>
+        <item name="android:tint">?attr/workspaceTextColor</item>
+        <item name="android:drawablePadding">4dp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/xml/overview_file_provider_paths.xml b/quickstep/res/xml/overview_file_provider_paths.xml
new file mode 100644
index 0000000..14d7459
--- /dev/null
+++ b/quickstep/res/xml/overview_file_provider_paths.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <cache-path name="shared_images" path="/" />
+    <files-path name="log_files" path="/" />
+</paths>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index ec66f11..83c67bb 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -43,8 +43,9 @@
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.proxy.ProxyActivityStarter;
 import com.android.launcher3.proxy.StartActivityParams;
+import com.android.launcher3.statehandlers.BackButtonAlphaHandler;
+import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.uioverrides.BackButtonAlphaHandler;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.RecentsModel;
@@ -67,6 +68,7 @@
 public abstract class BaseQuickstepLauncher extends Launcher
         implements NavigationModeChangeListener {
 
+    private DepthController mDepthController = new DepthController(this);
     protected SystemActions mSystemActions;
 
     /**
@@ -249,6 +251,10 @@
                 new BackButtonAlphaHandler(this)};
     }
 
+    public DepthController getDepthController() {
+        return mDepthController;
+    }
+
     @Override
     protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() {
         if (SysUINavigationMode.getMode(this) == Mode.NO_BUTTON) {
@@ -294,6 +300,10 @@
             onLauncherStateOrFocusChanged();
         }
 
+        if ((changeBits & ACTIVITY_STATE_STARTED) != 0) {
+            mDepthController.setActivityStarted(isStarted());
+        }
+
         super.onActivityFlagsChanged(changeBits);
     }
 
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index c93a4ba..a30e102 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -33,7 +33,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
-import static com.android.launcher3.uioverrides.DepthController.DEPTH;
+import static com.android.launcher3.statehandlers.DepthController.DEPTH;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
@@ -72,7 +72,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.launcher3.uioverrides.DepthController;
+import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
@@ -622,7 +622,7 @@
             backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    depthController.setSurfaceToLauncher(mLauncher.getDragLayer());
+                    depthController.setSurfaceToApp(null);
                 }
             });
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
similarity index 95%
rename from quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
rename to quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
index e82a504..983702a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.statehandlers;
 
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.AnimatedFloat.VALUE;
@@ -29,6 +29,9 @@
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SystemUiProxy;
 
+/**
+ * State handler for animating back button alpha
+ */
 public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler {
 
     private final BaseQuickstepLauncher mLauncher;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
similarity index 84%
rename from quickstep/src/com/android/launcher3/uioverrides/DepthController.java
rename to quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 8995a7e..24ba89a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.statehandlers;
 
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 
 import android.os.IBinder;
 import android.util.FloatProperty;
 import android.view.View;
+import android.view.ViewTreeObserver;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
@@ -77,6 +78,16 @@
         }
     }
 
+    private final ViewTreeObserver.OnDrawListener mOnDrawListener =
+            new ViewTreeObserver.OnDrawListener() {
+                @Override
+                public void onDraw() {
+                    View view = mLauncher.getDragLayer();
+                    setSurface(new SurfaceControlCompat(view));
+                    view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this));
+                }
+            };
+
     private final Launcher mLauncher;
     /**
      * Blur radius when completely zoomed out, in pixels.
@@ -103,21 +114,28 @@
     }
 
     /**
+     * Sets if the underlying activity is started or not
+     */
+    public void setActivityStarted(boolean isStarted) {
+        if (isStarted) {
+            mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener);
+        } else {
+            mLauncher.getDragLayer().getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
+            setSurface(null);
+        }
+    }
+
+    /**
      * Sets the specified app target surface to apply the blur to.
      */
     public void setSurfaceToApp(RemoteAnimationTargetCompat target) {
         if (target != null) {
             setSurface(target.leash);
+        } else {
+            setActivityStarted(mLauncher.isStarted());
         }
     }
 
-    /**
-     * Sets the surface to apply the blur to as the launcher surface.
-     */
-    public void setSurfaceToLauncher(View v) {
-        setSurface(v != null ? new SurfaceControlCompat(v) : null);
-    }
-
     private void setSurface(SurfaceControlCompat surface) {
         if (mSurface != surface) {
             mSurface = surface;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
index 5836ebd..010694b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
@@ -54,6 +54,9 @@
 
     @Override
     public void addChangeListener(Context context, Runnable r) {
+        if (mListeners == null) {
+            initialize(context);
+        }
         mListeners.add(r);
     }
 
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 2a569f5..94ef15a 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -32,8 +32,8 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.uioverrides.DepthController;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.ShelfPeekAnim;
 import com.android.systemui.shared.recents.model.ThumbnailData;
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 501c6f0..f7e40ca 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -64,6 +64,8 @@
     private static final String TAG = "GestureState";
 
     private static final ArrayList<String> STATE_NAMES = new ArrayList<>();
+    public static final GestureState DEFAULT_STATE = new GestureState();
+
     private static int FLAG_COUNT = 0;
     private static int getFlagForIndex(String name) {
         if (DEBUG_STATES) {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 1299a53..491c611 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -22,6 +22,7 @@
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
@@ -356,6 +357,7 @@
         return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
                 && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
                 && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
+                && (mSystemUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) == 0
                 && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
                         || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0);
     }
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
new file mode 100644
index 0000000..7760255
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Insets;
+import android.graphics.Picture;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+import androidx.core.content.FileProvider;
+
+import com.android.launcher3.BuildConfig;
+import com.android.quickstep.SystemUiProxy;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+
+/**
+ * Utility class containing methods to help manage image actions such as sharing, cropping, and
+ * saving image.
+ */
+public class ImageActionUtils {
+
+    private static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".overview.fileprovider";
+
+    /**
+     * Saves screenshot to location determine by SystemUiProxy
+     */
+    public static void saveScreenshot(SystemUiProxy systemUiProxy, Bitmap screenshot,
+            Rect screenshotBounds,
+            Insets visibleInsets, int taskId) {
+        systemUiProxy.handleImageAsScreenshot(screenshot, screenshotBounds, visibleInsets, taskId);
+    }
+
+    /**
+     * Launch the activity to share image.
+     */
+    @UiThread
+    public static void startShareActivity(Context context, Supplier<Bitmap> bitmapSupplier,
+            Rect crop, Intent intent, String tag) {
+        if (bitmapSupplier.get() == null) {
+            Log.e(tag, "No snapshot available, not starting share.");
+            return;
+        }
+
+        UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(context,
+                bitmapSupplier.get(), crop, intent, ImageActionUtils::getShareIntentForImageUri,
+                tag));
+    }
+
+    /**
+     * Starts activity based on given intent created from image uri.
+     */
+    @WorkerThread
+    public static void persistBitmapAndStartActivity(Context context, Bitmap bitmap, Rect crop,
+            Intent intent, BiFunction<Uri, Intent, Intent[]> uriToIntentMap, String tag) {
+        context.startActivities(
+                uriToIntentMap.apply(getImageUri(bitmap, crop, context, tag), intent));
+    }
+
+    /**
+     * Converts image bitmap to Uri by temporarily saving bitmap to cache, and creating Uri pointing
+     * to that location. Used to be able to share an image with another app.
+     *
+     * @param bitmap  The whole bitmap to be shared.
+     * @param crop    The section of the bitmap to be shared.
+     * @param context The application context, used to interact with file system.
+     * @param tag     Tag used to log errors.
+     * @return Uri that points to the cropped version of desired bitmap to share.
+     */
+    @WorkerThread
+    public static Uri getImageUri(Bitmap bitmap, Rect crop, Context context, String tag) {
+        Bitmap croppedBitmap = cropBitmap(bitmap, crop);
+        int cropHash = crop == null ? 0 : crop.hashCode();
+        String baseName = "image_" + bitmap.hashCode() + "_" + cropHash + ".png";
+        File file = new File(context.getCacheDir(), baseName);
+
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            croppedBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
+        } catch (IOException e) {
+            Log.e(tag, "Error saving image", e);
+        }
+
+        return FileProvider.getUriForFile(context, AUTHORITY, file);
+    }
+
+    /**
+     * Crops the bitmap to the provided size and returns a software backed bitmap whenever possible.
+     *
+     * @param bitmap The bitmap to be cropped.
+     * @param crop   The section of the bitmap in the crop.
+     * @return The cropped bitmap.
+     */
+    @WorkerThread
+    public static Bitmap cropBitmap(Bitmap bitmap, Rect crop) {
+        Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        if (crop == null) {
+            crop = new Rect(src);
+        }
+        if (crop.equals(src)) {
+            return bitmap;
+        } else {
+            if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
+                return Bitmap.createBitmap(bitmap, crop.left, crop.top, crop.width(),
+                        crop.height());
+            }
+
+            // For hardware bitmaps, use the Picture API to directly create a software bitmap
+            Picture picture = new Picture();
+            Canvas canvas = picture.beginRecording(crop.width(), crop.height());
+            canvas.drawBitmap(bitmap, -crop.left, -crop.top, null);
+            picture.endRecording();
+            return Bitmap.createBitmap(picture, crop.width(), crop.height(),
+                    Bitmap.Config.ARGB_8888);
+        }
+    }
+
+    /**
+     * Gets the intent used to share image.
+     */
+    @WorkerThread
+    private static Intent[] getShareIntentForImageUri(Uri uri, Intent intent) {
+        if (intent == null) {
+            intent = new Intent();
+        }
+        intent.setAction(Intent.ACTION_SEND)
+                .setComponent(null)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .setType("image/png")
+                .setData(uri)
+                .setFlags(FLAG_GRANT_READ_URI_PERMISSION)
+                .putExtra(Intent.EXTRA_STREAM, uri);
+        return new Intent[]{Intent.createChooser(intent, null).addFlags(FLAG_ACTIVITY_NEW_TASK)};
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
index 8e4762d..5904fcd 100644
--- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
+++ b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
@@ -88,7 +88,6 @@
      */
     @Test
     public void testPredictionExistsInAllApps() {
-        mDevice.pressHome();
         mLauncher.pressHome().switchToAllApps();
 
         // Dispatch an update
diff --git a/res/drawable-v24/ic_block_shadow.xml b/res/drawable-v24/ic_block_shadow.xml
new file mode 100644
index 0000000..045fe8d
--- /dev/null
+++ b/res/drawable-v24/ic_block_shadow.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.graphics.ShadowDrawable
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/ic_block_no_shadow"
+    android:elevation="@dimen/drop_target_shadow_elevation" />
diff --git a/res/drawable/ic_block.xml b/res/drawable/ic_block_no_shadow.xml
similarity index 100%
rename from res/drawable/ic_block.xml
rename to res/drawable/ic_block_no_shadow.xml
diff --git a/res/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
index cf1e835..f64b2d9 100644
--- a/res/layout/work_apps_paused.xml
+++ b/res/layout/work_apps_paused.xml
@@ -20,7 +20,7 @@
     android:gravity="center">
 
     <TextView
-        style="@style/TextHeadline"
+        style="@style/PrimaryMediumText"
         android:textColor="?attr/workProfileOverlayTextColor"
         android:id="@+id/work_apps_paused_title"
         android:layout_width="wrap_content"
diff --git a/res/values/drawables.xml b/res/values/drawables.xml
index 1367174..9c57ec1 100644
--- a/res/values/drawables.xml
+++ b/res/values/drawables.xml
@@ -17,4 +17,5 @@
     <drawable name="ic_setup_shadow">@drawable/ic_setting</drawable>
     <drawable name="ic_remove_shadow">@drawable/ic_remove_no_shadow</drawable>
     <drawable name="ic_uninstall_shadow">@drawable/ic_uninstall_no_shadow</drawable>
+    <drawable name="ic_block_shadow">@drawable/ic_block_no_shadow</drawable>
 </resources>
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 38e1201..9e8441a 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -25,6 +26,7 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
 
@@ -177,6 +179,10 @@
     public void onScrollStateChanged(int state) {
         super.onScrollStateChanged(state);
 
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onScrollStateChanged: " + state);
+        }
+
         if (state == SCROLL_STATE_IDLE) {
             AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
         }
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 336e423..b75a5e7 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -79,7 +79,7 @@
      * Add an app or shortcut for a specified rank.
      */
     public void add(WorkspaceItemInfo item, int rank, boolean animate) {
-        rank = Utilities.boundToRange(rank, 0, contents.size() + 1);
+        rank = Utilities.boundToRange(rank, 0, contents.size());
         contents.add(rank, item);
         for (int i = 0; i < mListeners.size(); i++) {
             mListeners.get(i).onAdd(item, rank);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index a83a694..c00a679 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -77,7 +77,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
 import android.widget.Toast;
@@ -115,6 +114,7 @@
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.pm.PinRequestHelper;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.popup.SystemShortcut;
@@ -124,7 +124,6 @@
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.AllAppsSwipeController;
 import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.uioverrides.DepthController;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -139,6 +138,7 @@
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.PendingRequestArgs;
+import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.ShortcutUtil;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
@@ -326,20 +326,10 @@
     private boolean mDeferOverlayCallbacks;
     private final Runnable mDeferredOverlayCallbacks = this::checkIfOverlayStillDeferred;
 
-    private DepthController mDepthController =
-            new DepthController(this);
-
-    private final ViewTreeObserver.OnDrawListener mOnDrawListener =
-            new ViewTreeObserver.OnDrawListener() {
-                @Override
-                public void onDraw() {
-                    getDepthController().setSurfaceToLauncher(mDragLayer);
-                    mDragLayer.post(() -> mDragLayer.getViewTreeObserver().removeOnDrawListener(
-                            this));
-                }
-            };
-
     private long mLastTouchUpTime = -1;
+    private boolean mTouchInProgress;
+
+    private SafeCloseable mUserChangedCallbackCloseable;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -459,6 +449,9 @@
         });
 
         TraceHelper.INSTANCE.endSection(traceToken);
+
+        mUserChangedCallbackCloseable = UserCache.INSTANCE.get(this).addUserChangeListener(
+                () -> getStateManager().goToState(NORMAL));
     }
 
     protected LauncherOverlayManager getDefaultOverlay() {
@@ -760,8 +753,8 @@
                     data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
             if (resultCode == RESULT_CANCELED) {
                 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId, requestArgs);
-                mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
-                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+                mWorkspace.removeExtraEmptyScreenDelayed(
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
             } else if (resultCode == RESULT_OK) {
                 addAppWidgetImpl(
                         appWidgetId, requestArgs, null,
@@ -791,15 +784,9 @@
                         "returned from the widget configuration activity.");
                 result = RESULT_CANCELED;
                 completeTwoStageWidgetDrop(result, appWidgetId, requestArgs);
-                final Runnable onComplete = new Runnable() {
-                    @Override
-                    public void run() {
-                        getStateManager().goToState(NORMAL);
-                    }
-                };
-
-                mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
-                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+                mWorkspace.removeExtraEmptyScreenDelayed(
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false,
+                        () -> getStateManager().goToState(NORMAL));
             } else {
                 if (requestArgs.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
                     // When the screen id represents an actual screen (as opposed to a rank)
@@ -818,8 +805,8 @@
                         dropLayout.setDropPending(false);
                     }
                 };
-                mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
-                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+                mWorkspace.removeExtraEmptyScreenDelayed(
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, onComplete);
             }
             return;
         }
@@ -838,12 +825,12 @@
             // Handle custom shortcuts created using ACTION_CREATE_SHORTCUT.
             if (resultCode == RESULT_OK && requestArgs.container != ItemInfo.NO_ID) {
                 completeAdd(requestCode, data, -1, requestArgs);
-                mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
-                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+                mWorkspace.removeExtraEmptyScreenDelayed(
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
 
             } else if (resultCode == RESULT_CANCELED) {
-                mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
-                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+                mWorkspace.removeExtraEmptyScreenDelayed(
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
             }
         }
 
@@ -942,8 +929,6 @@
         final int origDragLayerChildCount = mDragLayer.getChildCount();
         super.onStop();
 
-        mDragLayer.getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
-
         if (mDeferOverlayCallbacks) {
             checkIfOverlayStillDeferred();
         } else {
@@ -956,7 +941,6 @@
 
         NotificationListener.removeNotificationsChangedListener();
         getStateManager().moveToRestState();
-        getDepthController().setSurfaceToLauncher(null);
 
         // Workaround for b/78520668, explicitly trim memory once UI is hidden
         onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
@@ -974,6 +958,8 @@
                 }
             });
         }
+
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Activity.onStop");
     }
 
     @Override
@@ -984,10 +970,10 @@
         if (!mDeferOverlayCallbacks) {
             mOverlayManager.onActivityStarted(this);
         }
-        mDragLayer.getViewTreeObserver().addOnDrawListener(mOnDrawListener);
 
         mAppWidgetHost.setListenIfResumed(true);
         TraceHelper.INSTANCE.endSection(traceToken);
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Activity.onStart");
     }
 
     private void handleDeferredResume() {
@@ -1207,7 +1193,6 @@
         mScrimView = findViewById(R.id.scrim_view);
 
         // Setup the drag controller (drop targets have to be added in reverse order in priority)
-        mDragController.setMoveTarget(mWorkspace);
         mDropTargetBar.setup(mDragController);
 
         mAllAppsController.setupViews(mAppsView);
@@ -1507,11 +1492,7 @@
             target.pageIndex = mWorkspace.getCurrentPage();
             ued.logActionCommand(Action.Command.HOME_INTENT, target,
                     newContainerTarget(ContainerType.WORKSPACE));
-
-            final View v = getWindow().peekDecorView();
-            if (v != null && v.getWindowToken() != null) {
-                UiThreadHelper.hideKeyboardAsync(this, v.getWindowToken());
-            }
+            hideKeyboard();
 
             if (mLauncherCallbacks != null) {
                 mLauncherCallbacks.onHomeIntent(internalStateHandled);
@@ -1522,6 +1503,16 @@
         TraceHelper.INSTANCE.endSection(traceToken);
     }
 
+    /**
+     * Hides the keyboard if visible
+     */
+    public void hideKeyboard() {
+        final View v = getWindow().peekDecorView();
+        if (v != null && v.getWindowToken() != null) {
+            UiThreadHelper.hideKeyboardAsync(this, v.getWindowToken());
+        }
+    }
+
     @Override
     public void onRestoreInstanceState(Bundle state) {
         super.onRestoreInstanceState(state);
@@ -1589,6 +1580,7 @@
 
         mOverlayManager.onActivityDestroyed(this);
         mAppTransitionManager.unregisterRemoteAnimations();
+        mUserChangedCallbackCloseable.close();
     }
 
     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1682,7 +1674,7 @@
             };
             completeAddAppWidget(appWidgetId, info, boundWidget,
                     addFlowHandler.getProviderInfo(this));
-            mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
+            mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
         }
     }
 
@@ -1840,13 +1832,28 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_UP) {
-            mLastTouchUpTime = System.currentTimeMillis();
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mTouchInProgress = true;
+                break;
+            case MotionEvent.ACTION_UP:
+                mLastTouchUpTime = System.currentTimeMillis();
+                // Follow through
+            case MotionEvent.ACTION_CANCEL:
+                mTouchInProgress = false;
+                break;
         }
         TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
         return super.dispatchTouchEvent(ev);
     }
 
+    /**
+     * Returns true if a touch interaction is in progress
+     */
+    public boolean isTouchInProgress() {
+        return mTouchInProgress;
+    }
+
     @Override
     public void onBackPressed() {
         if (finishAutoCancelActionMode()) {
@@ -2112,7 +2119,7 @@
         }
 
         // Remove the extra empty screen
-        mWorkspace.removeExtraEmptyScreen(false, false);
+        mWorkspace.removeExtraEmptyScreen(false);
     }
 
     /**
@@ -2716,8 +2723,7 @@
     }
 
     protected StateHandler[] createStateHandlers() {
-        return new StateHandler[] { getAllAppsController(), getWorkspace(),
-                getDepthController() };
+        return new StateHandler[] { getAllAppsController(), getWorkspace() };
     }
 
     public TouchController[] createTouchControllers() {
@@ -2754,10 +2760,6 @@
         return Stream.of(APP_INFO, WIDGETS, INSTALL);
     }
 
-    public DepthController getDepthController() {
-        return mDepthController;
-    }
-
     public static Launcher getLauncher(Context context) {
         return fromContext(context);
     }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index a699c32..8d20bd6 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -85,6 +85,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Locale;
+import java.util.function.Supplier;
 
 public class LauncherProvider extends ContentProvider {
     private static final String TAG = "LauncherProvider";
@@ -145,7 +146,7 @@
      */
     protected synchronized void createDbIfNotExists() {
         if (mOpenHelper == null) {
-            mOpenHelper = new DatabaseHelper(getContext());
+            mOpenHelper = DatabaseHelper.createDatabaseHelper(getContext());
 
             if (RestoreDbTask.isPending(getContext())) {
                 if (!RestoreDbTask.performRestore(getContext(), mOpenHelper,
@@ -159,17 +160,17 @@
         }
     }
 
-    private synchronized boolean updateCurrentOpenHelper() {
-        final InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
-        if (TextUtils.equals(idp.dbFile, mOpenHelper.getDatabaseName())) {
+    private synchronized boolean prepForMigration(String dbFile, String targetTableName,
+            Supplier<DatabaseHelper> src, Supplier<DatabaseHelper> dst) {
+        if (TextUtils.equals(dbFile, mOpenHelper.getDatabaseName())) {
             return false;
         }
 
-        DatabaseHelper oldHelper = mOpenHelper;
-        mOpenHelper = new DatabaseHelper(getContext());
-        copyTable(oldHelper.getReadableDatabase(), Favorites.TABLE_NAME,
-                mOpenHelper.getWritableDatabase(), Favorites.TMP_TABLE, getContext());
-        oldHelper.close();
+        final DatabaseHelper helper = src.get();
+        mOpenHelper = dst.get();
+        copyTable(helper.getReadableDatabase(), Favorites.TABLE_NAME,
+                mOpenHelper.getWritableDatabase(), targetTableName, getContext());
+        helper.close();
         return true;
     }
 
@@ -425,7 +426,23 @@
                 if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
                     Bundle result = new Bundle();
                     result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
-                            updateCurrentOpenHelper());
+                            prepForMigration(
+                                    InvariantDeviceProfile.INSTANCE.get(getContext()).dbFile,
+                                    Favorites.TMP_TABLE,
+                                    () -> mOpenHelper,
+                                    () -> DatabaseHelper.createDatabaseHelper(getContext())));
+                    return result;
+                }
+            }
+            case LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW: {
+                if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
+                    Bundle result = new Bundle();
+                    result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
+                            prepForMigration(
+                                    arg /* dbFile */,
+                                    Favorites.PREVIEW_TABLE_NAME,
+                                    () -> DatabaseHelper.createDatabaseHelper(getContext(), arg),
+                                    () -> mOpenHelper));
                     return result;
                 }
             }
@@ -596,23 +613,31 @@
         private int mMaxScreenId = -1;
         private boolean mBackupTableExists;
 
-        DatabaseHelper(Context context) {
-            this(context, MULTI_DB_GRID_MIRATION_ALGO.get() ? InvariantDeviceProfile.INSTANCE.get(
-                    context).dbFile : LauncherFiles.LAUNCHER_DB);
+        static DatabaseHelper createDatabaseHelper(Context context) {
+            return createDatabaseHelper(context, null);
+        }
+
+        static DatabaseHelper createDatabaseHelper(Context context, String dbName) {
+            if (dbName == null) {
+                dbName = MULTI_DB_GRID_MIRATION_ALGO.get() ? InvariantDeviceProfile.INSTANCE.get(
+                        context).dbFile : LauncherFiles.LAUNCHER_DB;
+            }
+            DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName);
             // Table creation sometimes fails silently, which leads to a crash loop.
             // This way, we will try to create a table every time after crash, so the device
             // would eventually be able to recover.
-            if (!tableExists(getReadableDatabase(), Favorites.TABLE_NAME)) {
+            if (!tableExists(databaseHelper.getReadableDatabase(), Favorites.TABLE_NAME)) {
                 Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate");
                 // This operation is a no-op if the table already exists.
-                addFavoritesTable(getWritableDatabase(), true);
+                databaseHelper.addFavoritesTable(databaseHelper.getWritableDatabase(), true);
             }
             if (!MULTI_DB_GRID_MIRATION_ALGO.get()) {
-                mBackupTableExists = tableExists(getReadableDatabase(),
-                        Favorites.BACKUP_TABLE_NAME);
+                databaseHelper.mBackupTableExists = tableExists(
+                        databaseHelper.getReadableDatabase(), Favorites.BACKUP_TABLE_NAME);
             }
 
-            initIds();
+            databaseHelper.initIds();
+            return databaseHelper;
         }
 
         /**
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index f516446..5262b18 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -326,10 +326,16 @@
 
         public static final String METHOD_UPDATE_CURRENT_OPEN_HELPER = "update_current_open_helper";
 
+        public static final String METHOD_PREP_FOR_PREVIEW = "prep_for_preview";
+
         public static final String EXTRA_VALUE = "value";
 
         public static Bundle call(ContentResolver cr, String method) {
-            return cr.call(CONTENT_URI, method, null, null);
+            return call(cr, method, null);
+        }
+
+        public static Bundle call(ContentResolver cr, String method, String arg) {
+            return cr.call(CONTENT_URI, method, arg, null);
         }
     }
 }
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 24d0c41..e071777 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -324,6 +324,7 @@
 
     public AnimatorPlaybackController createAnimationToNewWorkspace(LauncherState state,
             StateAnimationConfig config) {
+        config.userControlled = true;
         mConfig.reset();
         config.copyTo(mConfig);
         mConfig.playbackController = createAnimationToNewWorkspaceInternal(state)
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 2430d5e..983c289 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -108,7 +108,7 @@
             updateText(R.string.uninstall_drop_target_label);
         } else if (action == DISMISS_PREDICTION) {
             mHoverColor = Themes.getColorAccent(getContext());
-            setDrawable(R.drawable.ic_block);
+            setDrawable(R.drawable.ic_block_shadow);
             updateText(R.string.dismiss_prediction_label);
         } else if (action == RECONFIGURE) {
             mHoverColor = Themes.getColorAccent(getContext());
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index c42e480..ee9c099 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -29,7 +29,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.LayoutTransition;
-import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.SuppressLint;
@@ -43,7 +42,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Message;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -125,9 +123,6 @@
 
     private static final boolean ENFORCE_DRAG_EVENT_ORDER = false;
 
-    private static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
-    private static final int FADE_EMPTY_SCREEN_DURATION = 150;
-
     private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
 
     private static final int DEFAULT_PAGE = 0;
@@ -140,7 +135,6 @@
     @Thunk final IntSparseArrayMap<CellLayout> mWorkspaceScreens = new IntSparseArrayMap<>();
     @Thunk final IntArray mScreenOrder = new IntArray();
 
-    @Thunk Runnable mRemoveEmptyScreenRunnable;
     @Thunk boolean mDeferRemoveExtraEmptyScreen = false;
 
     /**
@@ -428,7 +422,7 @@
         }
 
         if (!mDeferRemoveExtraEmptyScreen) {
-            removeExtraEmptyScreen(true, mDragSourceInternal != null);
+            removeExtraEmptyScreen(mDragSourceInternal != null);
         }
 
         updateChildrenLayersEnabled();
@@ -453,8 +447,16 @@
     private void setupLayoutTransition() {
         // We want to show layout transitions when pages are deleted, to close the gap.
         mLayoutTransition = new LayoutTransition();
+
         mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
         mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+        // Change the interpolators such that the fade animation plays before the move animation.
+        // This prevents empty adjacent pages to overlay during animation
+        mLayoutTransition.setInterpolator(LayoutTransition.DISAPPEARING,
+                Interpolators.clampToProgress(Interpolators.ACCEL_DEACCEL, 0, 0.5f));
+        mLayoutTransition.setInterpolator(LayoutTransition.CHANGE_DISAPPEARING,
+                Interpolators.clampToProgress(Interpolators.ACCEL_DEACCEL, 0.5f, 1));
+
         mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
         mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
         setLayoutTransition(mLayoutTransition);
@@ -571,9 +573,6 @@
         boolean lastChildOnScreen = false;
         boolean childOnFinalScreen = false;
 
-        // Cancel any pending removal of empty screen
-        mRemoveEmptyScreenRunnable = null;
-
         if (mDragSourceInternal != null) {
             if (mDragSourceInternal.getChildCount() == 1) {
                 lastChildOnScreen = true;
@@ -624,43 +623,34 @@
         }
     }
 
-    public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) {
-        removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens);
+    public void removeExtraEmptyScreen(boolean stripEmptyScreens) {
+        removeExtraEmptyScreenDelayed(0, stripEmptyScreens, null);
     }
 
-    public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete,
-            final int delay, final boolean stripEmptyScreens) {
+    public void removeExtraEmptyScreenDelayed(
+            int delay, boolean stripEmptyScreens, Runnable onComplete) {
         if (mLauncher.isWorkspaceLoading()) {
             // Don't strip empty screens if the workspace is still loading
             return;
         }
 
         if (delay > 0) {
-            postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens);
-                }
-            }, delay);
+            postDelayed(
+                    () -> removeExtraEmptyScreenDelayed(0, stripEmptyScreens, onComplete), delay);
             return;
         }
 
         convertFinalScreenToEmptyScreenIfNecessary();
         if (hasExtraEmptyScreen()) {
-            int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
-            if (getNextPage() == emptyIndex) {
-                snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION);
-                fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION,
-                        onComplete, stripEmptyScreens);
-            } else {
-                snapToPage(getNextPage(), 0);
-                fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION,
-                        onComplete, stripEmptyScreens);
-            }
-            return;
-        } else if (stripEmptyScreens) {
-            // If we're not going to strip the empty screens after removing
-            // the extra empty screen, do it right away.
+            removeView(mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID));
+            mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
+            mScreenOrder.removeValue(EXTRA_EMPTY_SCREEN_ID);
+
+            // Update the page indicator to reflect the removed page.
+            showPageIndicatorAtCurrentScroll();
+        }
+
+        if (stripEmptyScreens) {
             stripEmptyScreens();
         }
 
@@ -669,44 +659,6 @@
         }
     }
 
-    private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
-            final boolean stripEmptyScreens) {
-        // XXX: Do we need to update LM workspace screens below?
-        final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
-
-        mRemoveEmptyScreenRunnable = new Runnable() {
-            @Override
-            public void run() {
-                if (hasExtraEmptyScreen()) {
-                    mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
-                    mScreenOrder.removeValue(EXTRA_EMPTY_SCREEN_ID);
-                    removeView(cl);
-                    if (stripEmptyScreens) {
-                        stripEmptyScreens();
-                    }
-                    // Update the page indicator to reflect the removed page.
-                    showPageIndicatorAtCurrentScroll();
-                }
-            }
-        };
-
-        ObjectAnimator oa = ObjectAnimator.ofFloat(cl, ALPHA, 0f);
-        oa.setDuration(duration);
-        oa.setStartDelay(delay);
-        oa.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (mRemoveEmptyScreenRunnable != null) {
-                    mRemoveEmptyScreenRunnable.run();
-                }
-                if (onComplete != null) {
-                    onComplete.run();
-                }
-            }
-        });
-        oa.start();
-    }
-
     public boolean hasExtraEmptyScreen() {
         return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && getChildCount() > 1;
     }
@@ -793,8 +745,6 @@
             }
         }
 
-        boolean isInAccessibleDrag = mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
-
         // We enforce at least one page to add new items to. In the case that we remove the last
         // such screen, we convert the last screen to the empty screen
         int minScreens = 1;
@@ -813,7 +763,6 @@
                 removeView(cl);
             } else {
                 // if this is the last screen, convert it to the empty screen
-                mRemoveEmptyScreenRunnable = null;
                 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
                 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
             }
@@ -1224,10 +1173,8 @@
 
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        IBinder windowToken = getWindowToken();
-        mWallpaperOffset.setWindowToken(windowToken);
+        mWallpaperOffset.setWindowToken(getWindowToken());
         computeScroll();
-        mDragController.setWindowToken(windowToken);
     }
 
     protected void onDetachedFromWindow() {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index dd0212a..8d1a102 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -406,6 +406,11 @@
             setupWorkToggle();
             mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
             mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
+            mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN);
+            findViewById(R.id.tab_personal)
+                    .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
+            findViewById(R.id.tab_work)
+                    .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
             onTabChanged(mViewPager.getNextPage());
         } else {
             mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
@@ -456,16 +461,10 @@
 
     public void onTabChanged(int pos) {
         mHeader.setMainActive(pos == 0);
-        reset(true /* animate */);
-        mViewPager.getPageIndicator().updateTabTextColor(pos);
         if (mAH[pos].recyclerView != null) {
             mAH[pos].recyclerView.bindFastScrollbar();
-
-            findViewById(R.id.tab_personal)
-                    .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
-            findViewById(R.id.tab_work)
-                    .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
         }
+        reset(true /* animate */);
         if (mWorkModeSwitch != null) {
             mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK);
         }
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index 3e40392..2515c24 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -135,6 +135,7 @@
     @Override
     public void setActiveMarker(int activePage) {
         updateTabTextColor(activePage);
+        updateIndicatorPosition(activePage);
         if (mContainerView != null && mLastActivePage != activePage) {
             mContainerView.onTabChanged(activePage);
         }
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 1d32d1d..737c97b 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -75,6 +75,9 @@
     }
 
     public static void sendScrollFinishedEventToTest(Context context) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "sendScrollFinishedEventToTest");
+        }
         final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
         if (accessibilityManager == null) return;
 
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 4df3b0a..92f5112 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -154,7 +154,7 @@
             "Replace Smartspace with a version rendered by System UI.");
 
     public static final BooleanFlag ENABLE_LSQ_VELOCITY_PROVIDER = getDebugFlag(
-            "ENABLE_LSQ_VELOCITY_PROVIDER", false,
+            "ENABLE_LSQ_VELOCITY_PROVIDER", true,
             "Use Least Square algorithm for motion pause detection.");
 
     public static final BooleanFlag ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS =
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index d0d9aaf..db61f59 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.Utilities.ATLEAST_Q;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
@@ -27,7 +28,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.os.IBinder;
 import android.view.DragEvent;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -44,7 +44,6 @@
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.TouchController;
-import com.android.launcher3.util.UiThreadHelper;
 
 import java.util.ArrayList;
 
@@ -64,7 +63,7 @@
     private final FlingToDeleteHelper mFlingToDeleteHelper;
 
     // temporaries to avoid gc thrash
-    private Rect mRectTemp = new Rect();
+    private final Rect mRectTemp = new Rect();
     private final int[] mCoordinatesTemp = new int[2];
 
     /**
@@ -86,21 +85,14 @@
     private DropTarget.DragObject mDragObject;
 
     /** Who can receive drop events */
-    private ArrayList<DropTarget> mDropTargets = new ArrayList<>();
-    private ArrayList<DragListener> mListeners = new ArrayList<>();
-
-    /** The window token used as the parent for the DragView. */
-    private IBinder mWindowToken;
-
-    private View mMoveTarget;
+    private final ArrayList<DropTarget> mDropTargets = new ArrayList<>();
+    private final ArrayList<DragListener> mListeners = new ArrayList<>();
 
     private DropTarget mLastDropTarget;
 
     private int mLastTouchClassification;
     private int mDistanceSinceScroll = 0;
 
-    private Rect mDragLayerRect = new Rect();
-
     private boolean mIsInPreDrag;
 
     /**
@@ -153,8 +145,7 @@
             android.os.Debug.startMethodTracing("Launcher");
         }
 
-        // Hide soft keyboard, if visible
-        UiThreadHelper.hideKeyboardAsync(mLauncher, mWindowToken);
+        mLauncher.hideKeyboard();
         AbstractFloatingView.closeOpenViews(mLauncher, false, TYPE_DISCOVERY_BOUNCE);
 
         mOptions = options;
@@ -217,6 +208,11 @@
 
         handleMoveEvent(mLastTouch.x, mLastTouch.y);
         mLauncher.getUserEventDispatcher().resetActionDurationMillis();
+
+        if (!mLauncher.isTouchInProgress() && options.simulatedDndStartPoint == null) {
+            // If it is an internal drag and the touch is already complete, cancel immediately
+            MAIN_EXECUTOR.submit(this::cancelDrag);
+        }
         return dragView;
     }
 
@@ -362,9 +358,9 @@
      * Clamps the position to the drag layer bounds.
      */
     private Point getClampedDragLayerPos(float x, float y) {
-        mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect);
-        mTmpPoint.x = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1));
-        mTmpPoint.y = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1));
+        mLauncher.getDragLayer().getLocalVisibleRect(mRectTemp);
+        mTmpPoint.x = (int) Math.max(mRectTemp.left, Math.min(x, mRectTemp.right - 1));
+        mTmpPoint.y = (int) Math.max(mRectTemp.top, Math.min(y, mRectTemp.bottom - 1));
         return mTmpPoint;
     }
 
@@ -439,17 +435,6 @@
         return mDragDriver != null && mDragDriver.onDragEvent(event);
     }
 
-    /**
-     * Sets the view that should handle move events.
-     */
-    public void setMoveTarget(View view) {
-        mMoveTarget = view;
-    }
-
-    public boolean dispatchUnhandledMove(View focused, int direction) {
-        return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
-    }
-
     private void handleMoveEvent(int x, int y) {
         mDragObject.dragView.move(x, y);
 
@@ -593,10 +578,6 @@
         return mLauncher.getWorkspace();
     }
 
-    public void setWindowToken(IBinder token) {
-        mWindowToken = token;
-    }
-
     /**
      * Sets the drag listener which will be notified when a drag starts or ends.
      */
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index e18ca54..9ece3d3 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -21,6 +21,7 @@
 import static android.view.View.MeasureSpec.getMode;
 import static android.view.View.MeasureSpec.getSize;
 
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 
 import android.animation.Animator;
@@ -49,7 +50,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.OverviewScrim;
 import com.android.launcher3.graphics.RotationMode;
@@ -74,11 +74,11 @@
     public static final int ANIMATION_END_DISAPPEAR = 0;
     public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
 
-    @Thunk DragController mDragController;
+    private DragController mDragController;
 
     // Variables relating to animation of views after drop
     private ValueAnimator mDropAnim = null;
-    private final TimeInterpolator mCubicEaseOutInterpolator = Interpolators.DEACCEL_1_5;
+
     @Thunk DragView mDropView = null;
     @Thunk int mAnchorViewInitialScrollX = 0;
     @Thunk View mAnchorView = null;
@@ -88,13 +88,14 @@
     private int mTopViewIndex;
     private int mChildCountOnLastUpdate = -1;
 
-    private Rect mTmpRect = new Rect();
-
     // Related to adjacent page hints
     private final ViewGroupFocusHelper mFocusIndicatorHelper;
     private final WorkspaceAndHotseatScrim mWorkspaceScrim;
     private final OverviewScrim mOverviewScrim;
 
+    // View that should handle move events
+    private View mMoveTarget;
+
     /**
      * Used to create a new DragLayer from XML.
      *
@@ -116,6 +117,7 @@
     public void setup(DragController dragController, Workspace workspace) {
         mDragController = dragController;
         mWorkspaceScrim.setWorkspace(workspace);
+        mMoveTarget = workspace;
         recreateControllers();
     }
 
@@ -223,7 +225,7 @@
     @Override
     public boolean dispatchUnhandledMove(View focused, int direction) {
         return super.dispatchUnhandledMove(focused, direction)
-                || mDragController.dispatchUnhandledMove(focused, direction);
+                || mMoveTarget.dispatchUnhandledMove(focused, direction);
     }
 
     @Override
@@ -260,9 +262,10 @@
         parentChildren.measureChild(child);
         parentChildren.layoutChild(child);
 
-        getViewRectRelativeToSelf(dragView, mTmpRect);
-        final int fromX = mTmpRect.left;
-        final int fromY = mTmpRect.top;
+        Rect dragViewBounds = new Rect();
+        getViewRectRelativeToSelf(dragView, dragViewBounds);
+        final int fromX = dragViewBounds.left;
+        final int fromY = dragViewBounds.top;
 
         float coord[] = new float[2];
         float childScale = child.getScaleX();
@@ -283,15 +286,15 @@
 
         if (child instanceof DraggableView) {
             DraggableView d = (DraggableView) child;
-            d.getVisualDragBounds(mTmpRect);
+            d.getVisualDragBounds(dragViewBounds);
 
             // This accounts for the offset of the DragView created by scaling it about its
             // center as it animates into place.
             float scaleShiftX = dragView.getMeasuredWidth() * (1 - scale) / 2;
             float scaleShiftY = dragView.getMeasuredHeight() * (1 - scale) / 2;
 
-            toX += scale * (mTmpRect.left - dragView.getBlurSizeOutline() / 2) - scaleShiftX;
-            toY += scale * (mTmpRect.top - dragView.getBlurSizeOutline() / 2) - scaleShiftY;
+            toX += scale * (dragViewBounds.left - dragView.getBlurSizeOutline() / 2) - scaleShiftX;
+            toY += scale * (dragViewBounds.top - dragView.getBlurSizeOutline() / 2) - scaleShiftY;
         }
 
         child.setVisibility(INVISIBLE);
@@ -348,7 +351,7 @@
         if (duration < 0) {
             duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
             if (dist < maxDist) {
-                duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
+                duration *= DEACCEL_1_5.getInterpolation(dist / maxDist);
             }
             duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
         }
@@ -356,7 +359,7 @@
         // Fall back to cubic ease out interpolator for the animation if none is specified
         TimeInterpolator interpolator = null;
         if (alphaInterpolator == null || motionInterpolator == null) {
-            interpolator = mCubicEaseOutInterpolator;
+            interpolator = DEACCEL_1_5;
         }
 
         // Animate the view
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 14fa1f4..365e76f 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -81,6 +81,7 @@
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.WorkspaceItemInfo;
@@ -962,6 +963,7 @@
             View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
                     ? mCurrentDragView : mContent.createNewView(info);
             ArrayList<View> views = getIconsInReadingOrder();
+            info.rank = Utilities.boundToRange(info.rank, 0, views.size());
             views.add(info.rank, icon);
             mContent.arrangeChildren(views);
             mItemsInvalidated = true;
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 5bc6610..7d4eb0e 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -21,7 +21,6 @@
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER;
 import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
-import static com.android.launcher3.model.GridSizeMigrationTask.needsToMigrate;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -397,7 +396,10 @@
 
         private void populate() {
             if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
-                boolean needsToMigrate = needsToMigrate(mContext, mIdp);
+                boolean needsToMigrate =
+                        MULTI_DB_GRID_MIRATION_ALGO.get()
+                                ? GridSizeMigrationTaskV2.needsToMigrate(mContext, mIdp)
+                                : GridSizeMigrationTask.needsToMigrate(mContext, mIdp);
                 boolean success = false;
                 if (needsToMigrate) {
                     success = MULTI_DB_GRID_MIRATION_ALGO.get()
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index a9d10d7..1b70fde 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.logging;
 
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.NAVBAR;
-
 import android.util.ArrayMap;
 import android.util.SparseArray;
 import android.view.View;
@@ -27,12 +25,9 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.userevent.nano.LauncherLogExtensions.TargetExtension;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.userevent.nano.LauncherLogProto.TipType;
 import com.android.launcher3.util.InstantAppResolver;
 
 import java.lang.reflect.Field;
@@ -70,93 +65,6 @@
         return result != null ? result : UNKNOWN;
     }
 
-    public static String getActionStr(Action action) {
-        String str = "";
-        switch (action.type) {
-            case Action.Type.TOUCH:
-                str += getFieldName(action.touch, Action.Touch.class);
-                if (action.touch == Action.Touch.SWIPE || action.touch == Action.Touch.FLING) {
-                    str += " direction=" + getFieldName(action.dir, Action.Direction.class);
-                }
-                break;
-            case Action.Type.COMMAND:
-                str += getFieldName(action.command, Action.Command.class);
-                break;
-            default: return getFieldName(action.type, Action.Type.class);
-        }
-        if (action.touch == Action.Touch.SWIPE || action.touch == Action.Touch.FLING ||
-                (action.command == Action.Command.BACK && action.dir != Action.Direction.NONE)) {
-            str += " direction=" + getFieldName(action.dir, Action.Direction.class);
-        }
-        return str;
-    }
-
-    public static String getTargetStr(Target t) {
-        if (t == null) {
-            return "";
-        }
-        String str = "";
-        switch (t.type) {
-            case Target.Type.ITEM:
-                str = getItemStr(t);
-                break;
-            case Target.Type.CONTROL:
-                str = getFieldName(t.controlType, ControlType.class);
-                break;
-            case Target.Type.CONTAINER:
-                str = getFieldName(t.containerType, ContainerType.class);
-                if (t.containerType == ContainerType.WORKSPACE ||
-                        t.containerType == ContainerType.HOTSEAT ||
-                        t.containerType == NAVBAR) {
-                    str += " id=" + t.pageIndex;
-                } else if (t.containerType == ContainerType.FOLDER) {
-                    str += " grid(" + t.gridX + "," + t.gridY + ")";
-                }
-                break;
-            default:
-                str += "UNKNOWN TARGET TYPE";
-        }
-
-        if (t.spanX != 1 || t.spanY != 1) {
-            str += " span(" + t.spanX + "," + t.spanY + ")";
-        }
-
-        if (t.tipType != TipType.DEFAULT_NONE) {
-            str += " " + getFieldName(t.tipType, TipType.class);
-        }
-
-        return str;
-    }
-
-    private static String getItemStr(Target t) {
-        String typeStr = getFieldName(t.itemType, ItemType.class);
-        if (t.packageNameHash != 0) {
-            typeStr += ", packageHash=" + t.packageNameHash;
-        }
-        if (t.componentHash != 0) {
-            typeStr += ", componentHash=" + t.componentHash;
-        }
-        if (t.intentHash != 0) {
-            typeStr += ", intentHash=" + t.intentHash;
-        }
-        if (t.itemType == ItemType.FOLDER_ICON) {
-            typeStr += ", grid(" + t.gridX + "," + t.gridY + ")";
-        } else if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0)
-                && t.itemType != ItemType.TASK) {
-            typeStr +=
-                    ", isWorkApp=" + t.isWorkApp + ", predictiveRank=" + t.predictedRank + ", grid("
-                            + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
-                            + "), pageIdx=" + t.pageIndex;
-        }
-        if (t.searchQueryLength != 0) {
-            typeStr += ", searchQueryLength=" + t.searchQueryLength;
-        }
-        if (t.itemType == ItemType.TASK) {
-            typeStr += ", pageIdx=" + t.pageIndex;
-        }
-        return typeStr;
-    }
-
     public static Target newItemTarget(int itemType) {
         Target t = newTarget(Target.Type.ITEM);
         t.itemType = itemType;
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 298a58a..206688a 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -329,7 +329,8 @@
         // Now add the new shortcuts to the map.
         for (ShortcutInfo shortcut : shortcuts) {
             boolean shouldShowInContainer = shortcut.isEnabled()
-                    && (shortcut.isDeclaredInManifest() || shortcut.isDynamic());
+                    && (shortcut.isDeclaredInManifest() || shortcut.isDynamic())
+                    && shortcut.getActivity() != null;
             if (shouldShowInContainer) {
                 ComponentKey targetComponent
                         = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index 0bdccfa..79ae4c5 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -123,8 +123,16 @@
     }
 
     /**
-     * Run the migration algorithm if needed. For preview, we provide the intended idp because it
-     * has not been changed. If idp is null, we read it from the context, for actual grid migration.
+     * When migrating the grid for preview, we copy the table
+     * {@link LauncherSettings.Favorites.TABLE_NAME} into
+     * {@link LauncherSettings.Favorites.PREVIEW_TABLE_NAME}, run grid size migration from the
+     * former to the later, then use the later table for preview.
+     *
+     * Similarly when doing the actual grid migration, the former grid option's table
+     * {@link LauncherSettings.Favorites.TABLE_NAME} is copied into the new grid option's
+     * {@link LauncherSettings.Favorites.TMP_TABLE}, we then run the grid size migration algorithm
+     * to migrate the later to the former, and load the workspace from the default
+     * {@link LauncherSettings.Favorites.TABLE_NAME}.
      *
      * @return false if the migration failed.
      */
@@ -151,7 +159,14 @@
         HashSet<String> validPackages = getValidPackages(context);
         int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons);
 
-        if (!LauncherSettings.Settings.call(
+        if (migrateForPreview) {
+            if (!LauncherSettings.Settings.call(
+                    context.getContentResolver(),
+                    LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW, idp.dbFile).getBoolean(
+                    LauncherSettings.Settings.EXTRA_VALUE)) {
+                return false;
+            }
+        } else if (!LauncherSettings.Settings.call(
                 context.getContentResolver(),
                 LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER).getBoolean(
                 LauncherSettings.Settings.EXTRA_VALUE)) {
@@ -164,9 +179,13 @@
                 LauncherSettings.Settings.METHOD_NEW_TRANSACTION).getBinder(
                 LauncherSettings.Settings.EXTRA_VALUE)) {
 
-            DbReader srcReader = new DbReader(t.getDb(), LauncherSettings.Favorites.TMP_TABLE,
+            DbReader srcReader = new DbReader(t.getDb(),
+                    migrateForPreview ? LauncherSettings.Favorites.TABLE_NAME
+                            : LauncherSettings.Favorites.TMP_TABLE,
                     context, validPackages, srcHotseatCount);
-            DbReader destReader = new DbReader(t.getDb(), LauncherSettings.Favorites.TABLE_NAME,
+            DbReader destReader = new DbReader(t.getDb(),
+                    migrateForPreview ? LauncherSettings.Favorites.PREVIEW_TABLE_NAME
+                            : LauncherSettings.Favorites.TABLE_NAME,
                     context, validPackages, idp.numHotseatIcons);
 
             Point targetSize = new Point(idp.numColumns, idp.numRows);
@@ -174,7 +193,9 @@
                     srcReader, destReader, idp.numHotseatIcons, targetSize);
             task.migrate();
 
-            dropTable(t.getDb(), LauncherSettings.Favorites.TMP_TABLE);
+            if (!migrateForPreview) {
+                dropTable(t.getDb(), LauncherSettings.Favorites.TMP_TABLE);
+            }
 
             t.commit();
             return true;
@@ -186,11 +207,13 @@
             Log.v(TAG, "Workspace migration completed in "
                     + (System.currentTimeMillis() - migrationStartTime));
 
-            // Save current configuration, so that the migration does not run again.
-            prefs.edit()
-                    .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)
-                    .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)
-                    .apply();
+            if (!migrateForPreview) {
+                // Save current configuration, so that the migration does not run again.
+                prefs.edit()
+                        .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)
+                        .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)
+                        .apply();
+            }
         }
     }
 
@@ -202,7 +225,7 @@
 
         // Migrate hotseat
         HotseatPlacementSolution hotseatSolution = new HotseatPlacementSolution(mDb, mSrcReader,
-                mContext, mDestHotseatSize, mHotseatItems, mHotseatDiff);
+                mDestReader, mContext, mDestHotseatSize, mHotseatItems, mHotseatDiff);
         hotseatSolution.find();
 
         // Sort the items by the reading order.
@@ -215,7 +238,7 @@
             }
             List<DbEntry> entries = mDestReader.loadWorkspaceEntries(screenId);
             GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
-                    mContext, entries, screenId, mTrgX, mTrgY, mWorkspaceDiff);
+                    mDestReader, mContext, entries, screenId, mTrgX, mTrgY, mWorkspaceDiff);
             workspaceSolution.find();
             if (mWorkspaceDiff.isEmpty()) {
                 break;
@@ -225,7 +248,8 @@
         int screenId = mDestReader.mLastScreenId + 1;
         while (!mWorkspaceDiff.isEmpty()) {
             GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
-                    mContext, new ArrayList<>(), screenId, mTrgX, mTrgY, mWorkspaceDiff);
+                    mDestReader, mContext, new ArrayList<>(), screenId, mTrgX, mTrgY,
+                    mWorkspaceDiff);
             workspaceSolution.find();
             screenId++;
         }
@@ -246,7 +270,8 @@
     }
 
     private static void insertEntryInDb(SQLiteDatabase db, Context context,
-            ArrayList<DbEntry> entriesFromSrcDb, DbEntry entry) {
+            ArrayList<DbEntry> entriesFromSrcDb, DbEntry entry, String srcTableName,
+            String destTableName) {
         int id = -1;
         switch (entry.itemType) {
             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
@@ -283,8 +308,8 @@
                 return;
         }
 
-        Cursor c = db.query(LauncherSettings.Favorites.TMP_TABLE, null,
-                LauncherSettings.Favorites._ID + " = '" + id + "'", null, null, null, null);
+        Cursor c = db.query(srcTableName, null, LauncherSettings.Favorites._ID + " = '" + id + "'",
+                null, null, null, null);
 
         while (c.moveToNext()) {
             ContentValues values = new ContentValues();
@@ -294,14 +319,14 @@
                     LauncherSettings.Settings.call(context.getContentResolver(),
                             LauncherSettings.Settings.METHOD_NEW_ITEM_ID).getInt(
                             LauncherSettings.Settings.EXTRA_VALUE));
-            db.insert(LauncherSettings.Favorites.TABLE_NAME, null, values);
+            db.insert(destTableName, null, values);
         }
         c.close();
     }
 
-    private static void removeEntryFromDb(SQLiteDatabase db, IntArray entryId) {
-        db.delete(LauncherSettings.Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
-                LauncherSettings.Favorites._ID, entryId), null);
+    private static void removeEntryFromDb(SQLiteDatabase db, String tableName, IntArray entryId) {
+        db.delete(tableName,
+                Utilities.createDbSelectionQuery(LauncherSettings.Favorites._ID, entryId), null);
     }
 
     private static HashSet<String> getValidPackages(Context context) {
@@ -325,6 +350,7 @@
 
         private final SQLiteDatabase mDb;
         private final DbReader mSrcReader;
+        private final DbReader mDestReader;
         private final Context mContext;
         private final GridOccupancy mOccupied;
         private final int mScreenId;
@@ -335,11 +361,12 @@
         private int mNextStartX;
         private int mNextStartY;
 
-        GridPlacementSolution(SQLiteDatabase db, DbReader srcReader, Context context,
-                List<DbEntry> placedWorkspaceItems, int screenId, int trgX,
+        GridPlacementSolution(SQLiteDatabase db, DbReader srcReader, DbReader destReader,
+                Context context, List<DbEntry> placedWorkspaceItems, int screenId, int trgX,
                 int trgY, List<DbEntry> itemsToPlace) {
             mDb = db;
             mSrcReader = srcReader;
+            mDestReader = destReader;
             mContext = context;
             mOccupied = new GridOccupancy(trgX, trgY);
             mScreenId = screenId;
@@ -362,7 +389,8 @@
                     continue;
                 }
                 if (findPlacement(entry)) {
-                    insertEntryInDb(mDb, mContext, mSrcReader.mWorkspaceEntries, entry);
+                    insertEntryInDb(mDb, mContext, mSrcReader.mWorkspaceEntries, entry,
+                            mSrcReader.mTableName, mDestReader.mTableName);
                     iterator.remove();
                 }
             }
@@ -397,14 +425,17 @@
 
         private final SQLiteDatabase mDb;
         private final DbReader mSrcReader;
+        private final DbReader mDestReader;
         private final Context mContext;
         private final HotseatOccupancy mOccupied;
         private final List<DbEntry> mItemsToPlace;
 
-        HotseatPlacementSolution(SQLiteDatabase db, DbReader srcReader, Context context,
-                int hotseatSize, List<DbEntry> placedHotseatItems, List<DbEntry> itemsToPlace) {
+        HotseatPlacementSolution(SQLiteDatabase db, DbReader srcReader, DbReader destReader,
+                Context context, int hotseatSize, List<DbEntry> placedHotseatItems,
+                List<DbEntry> itemsToPlace) {
             mDb = db;
             mSrcReader = srcReader;
+            mDestReader = destReader;
             mContext = context;
             mOccupied = new HotseatOccupancy(hotseatSize);
             for (DbEntry entry : placedHotseatItems) {
@@ -422,7 +453,8 @@
                     // to something other than -1.
                     entry.cellX = i;
                     entry.cellY = 0;
-                    insertEntryInDb(mDb, mContext, mSrcReader.mHotseatEntries, entry);
+                    insertEntryInDb(mDb, mContext, mSrcReader.mHotseatEntries, entry,
+                            mSrcReader.mTableName, mDestReader.mTableName);
                     mOccupied.markCells(entry, true);
                 }
             }
@@ -519,7 +551,7 @@
                 }
                 mHotseatEntries.add(entry);
             }
-            removeEntryFromDb(mDb, entriesToRemove);
+            removeEntryFromDb(mDb, mTableName, entriesToRemove);
             c.close();
             return mHotseatEntries;
         }
@@ -639,7 +671,7 @@
                 }
                 mWorkspaceEntries.add(entry);
             }
-            removeEntryFromDb(mDb, entriesToRemove);
+            removeEntryFromDb(mDb, mTableName, entriesToRemove);
             c.close();
             return mWorkspaceEntries;
         }
@@ -657,7 +689,7 @@
                     total++;
                     entry.mFolderItems.add(intent);
                 } catch (Exception e) {
-                    removeEntryFromDb(mDb, IntArray.wrap(c.getInt(0)));
+                    removeEntryFromDb(mDb, mTableName, IntArray.wrap(c.getInt(0)));
                 }
             }
             c.close();
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index 678b647..f723256 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -95,7 +95,7 @@
 
     private void removeUserChangeListener(Runnable command) {
         synchronized (this) {
-            mUserChangeListeners.add(command);
+            mUserChangeListeners.remove(command);
             if (mUserChangeListeners.isEmpty()) {
                 // Disable cache and stop listening
                 mContext.unregisterReceiver(mUserChangeReceiver);
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index dacea84..7e05a5a 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -129,6 +129,7 @@
             toDb.execSQL("ATTACH DATABASE '" + fromDb.getPath() + "' AS from_db");
             toDb.execSQL(
                     "INSERT INTO " + toTable + " SELECT * FROM from_db." + fromTable);
+            toDb.execSQL("DETACH DATABASE 'from_db'");
         } else {
             toDb.execSQL("INSERT INTO " + toTable + " SELECT * FROM " + fromTable);
         }
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 97c67c5..2ba624c 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
 
+import android.content.Context;
 import android.graphics.Rect;
 
 import com.android.launcher3.DeviceProfile;
@@ -77,6 +78,11 @@
     }
 
     @Override
+    public float getDepth(Context context) {
+        return 0.5f;
+    }
+
+    @Override
     public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
         return new ScaleAndTranslation(1, 0, 0);
     }
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 97ce65e..e70b570 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -35,6 +35,7 @@
     public static final String TAPL_EVENTS_TAG = "TaplEvents";
     public static final String SEQUENCE_MAIN = "Main";
     public static final String SEQUENCE_TIS = "TIS";
+    public static final String SEQUENCE_PILFER = "Pilfer";
 
     public static String stateOrdinalToString(int ordinal) {
         switch (ordinal) {
@@ -95,4 +96,5 @@
 
     public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
     public static final String APP_NOT_DISABLED = "b/139891609";
+    public static final String NO_SCROLL_END_WIDGETS = "b/152354290";
 }
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
new file mode 100644
index 0000000..478141a
--- /dev/null
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.views;
+
+import static com.android.launcher3.Utilities.mapToRange;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Outline;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InsettableFrameLayout.LayoutParams;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
+import com.android.launcher3.graphics.IconShape;
+
+/**
+ * A view used to draw both layers of an {@link AdaptiveIconDrawable}.
+ * Supports springing just the foreground layer.
+ * Supports clipping the icon to/from its icon shape.
+ */
+@TargetApi(Build.VERSION_CODES.Q)
+public class ClipIconView extends View implements ClipPathView {
+
+    private static final Rect sTmpRect = new Rect();
+
+    // We spring the foreground drawable relative to the icon's movement in the DragLayer.
+    // We then use these two factor values to scale the movement of the fg within this view.
+    private static final int FG_TRANS_X_FACTOR = 60;
+    private static final int FG_TRANS_Y_FACTOR = 75;
+
+    private static final FloatPropertyCompat<ClipIconView> mFgTransYProperty =
+            new FloatPropertyCompat<ClipIconView>("ClipIconViewFgTransY") {
+                @Override
+                public float getValue(ClipIconView view) {
+                    return view.mFgTransY;
+                }
+
+                @Override
+                public void setValue(ClipIconView view, float transY) {
+                    view.mFgTransY = transY;
+                    view.invalidate();
+                }
+            };
+
+    private static final FloatPropertyCompat<ClipIconView> mFgTransXProperty =
+            new FloatPropertyCompat<ClipIconView>("ClipIconViewFgTransX") {
+                @Override
+                public float getValue(ClipIconView view) {
+                    return view.mFgTransX;
+                }
+
+                @Override
+                public void setValue(ClipIconView view, float transX) {
+                    view.mFgTransX = transX;
+                    view.invalidate();
+                }
+            };
+
+    private final Launcher mLauncher;
+    private final int mBlurSizeOutline;
+    private final boolean mIsRtl;
+
+    private @Nullable Drawable mForeground;
+    private @Nullable Drawable mBackground;
+
+    private boolean mIsVerticalBarLayout = false;
+    private boolean mIsAdaptiveIcon = false;
+
+    private ValueAnimator mRevealAnimator;
+
+    private final Rect mStartRevealRect = new Rect();
+    private final Rect mEndRevealRect = new Rect();
+    private Path mClipPath;
+    private float mTaskCornerRadius;
+
+    private final Rect mOutline = new Rect();
+    private final Rect mFinalDrawableBounds = new Rect();
+
+    private final SpringAnimation mFgSpringY;
+    private float mFgTransY;
+    private final SpringAnimation mFgSpringX;
+    private float mFgTransX;
+
+    public ClipIconView(Context context) {
+        this(context, null);
+    }
+
+    public ClipIconView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ClipIconView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(context);
+        mBlurSizeOutline = getResources().getDimensionPixelSize(
+                R.dimen.blur_size_medium_outline);
+        mIsRtl = Utilities.isRtl(getResources());
+
+        mFgSpringX = new SpringAnimation(this, mFgTransXProperty)
+                .setSpring(new SpringForce()
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+                        .setStiffness(SpringForce.STIFFNESS_LOW));
+        mFgSpringY = new SpringAnimation(this, mFgTransYProperty)
+                .setSpring(new SpringForce()
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+                        .setStiffness(SpringForce.STIFFNESS_LOW));
+    }
+
+    void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
+            boolean isOpening, float scale, float minSize, LayoutParams parentLp) {
+        DeviceProfile dp = mLauncher.getDeviceProfile();
+        float dX = mIsRtl
+                ? rect.left - (dp.widthPx - parentLp.getMarginStart() - parentLp.width)
+                : rect.left - parentLp.getMarginStart();
+        float dY = rect.top - parentLp.topMargin;
+
+        // shapeRevealProgress = 1 when progress = shapeProgressStart + SHAPE_PROGRESS_DURATION
+        float toMax = isOpening ? 1 / SHAPE_PROGRESS_DURATION : 1f;
+        float shapeRevealProgress = Utilities.boundToRange(mapToRange(
+                Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax,
+                LINEAR), 0, 1);
+
+        if (mIsVerticalBarLayout) {
+            mOutline.right = (int) (rect.width() / scale);
+        } else {
+            mOutline.bottom = (int) (rect.height() / scale);
+        }
+
+        mTaskCornerRadius = cornerRadius / scale;
+        if (mIsAdaptiveIcon) {
+            if (!isOpening && progress >= shapeProgressStart) {
+                if (mRevealAnimator == null) {
+                    mRevealAnimator = (ValueAnimator) IconShape.getShape().createRevealAnimator(
+                            this, mStartRevealRect, mOutline, mTaskCornerRadius, !isOpening);
+                    mRevealAnimator.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            mRevealAnimator = null;
+                        }
+                    });
+                    mRevealAnimator.start();
+                    // We pause here so we can set the current fraction ourselves.
+                    mRevealAnimator.pause();
+                }
+                mRevealAnimator.setCurrentFraction(shapeRevealProgress);
+            }
+
+            float drawableScale = (mIsVerticalBarLayout ? mOutline.width() : mOutline.height())
+                    / minSize;
+            setBackgroundDrawableBounds(drawableScale);
+            if (isOpening) {
+                // Center align foreground
+                int height = mFinalDrawableBounds.height();
+                int width = mFinalDrawableBounds.width();
+                int diffY = mIsVerticalBarLayout ? 0
+                        : (int) (((height * drawableScale) - height) / 2);
+                int diffX = mIsVerticalBarLayout ? (int) (((width * drawableScale) - width) / 2)
+                        : 0;
+                sTmpRect.set(mFinalDrawableBounds);
+                sTmpRect.offset(diffX, diffY);
+                mForeground.setBounds(sTmpRect);
+            } else {
+                // Spring the foreground relative to the icon's movement within the DragLayer.
+                int diffX = (int) (dX / dp.availableWidthPx * FG_TRANS_X_FACTOR);
+                int diffY = (int) (dY / dp.availableHeightPx * FG_TRANS_Y_FACTOR);
+
+                mFgSpringX.animateToFinalPosition(diffX);
+                mFgSpringY.animateToFinalPosition(diffY);
+            }
+        }
+        invalidate();
+        invalidateOutline();
+    }
+
+    private void setBackgroundDrawableBounds(float scale) {
+        sTmpRect.set(mFinalDrawableBounds);
+        Utilities.scaleRectAboutCenter(sTmpRect, scale);
+        // Since the drawable is at the top of the view, we need to offset to keep it centered.
+        if (mIsVerticalBarLayout) {
+            sTmpRect.offsetTo((int) (mFinalDrawableBounds.left * scale), sTmpRect.top);
+        } else {
+            sTmpRect.offsetTo(sTmpRect.left, (int) (mFinalDrawableBounds.top * scale));
+        }
+        mBackground.setBounds(sTmpRect);
+    }
+
+    protected void endReveal() {
+        if (mRevealAnimator != null) {
+            mRevealAnimator.end();
+        }
+    }
+
+    void setIcon(@Nullable Drawable drawable, int iconOffset, LayoutParams lp, boolean isOpening) {
+        mIsAdaptiveIcon = drawable instanceof AdaptiveIconDrawable;
+        if (mIsAdaptiveIcon) {
+            boolean isFolderIcon = drawable instanceof FolderAdaptiveIcon;
+
+            AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) drawable;
+            Drawable background = adaptiveIcon.getBackground();
+            if (background == null) {
+                background = new ColorDrawable(Color.TRANSPARENT);
+            }
+            mBackground = background;
+            Drawable foreground = adaptiveIcon.getForeground();
+            if (foreground == null) {
+                foreground = new ColorDrawable(Color.TRANSPARENT);
+            }
+            mForeground = foreground;
+
+            final int originalHeight = lp.height;
+            final int originalWidth = lp.width;
+
+            int blurMargin = mBlurSizeOutline / 2;
+            mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight);
+
+            if (!isFolderIcon) {
+                mFinalDrawableBounds.inset(iconOffset - blurMargin, iconOffset - blurMargin);
+            }
+            mForeground.setBounds(mFinalDrawableBounds);
+            mBackground.setBounds(mFinalDrawableBounds);
+
+            mStartRevealRect.set(0, 0, originalWidth, originalHeight);
+
+            if (!isFolderIcon) {
+                Utilities.scaleRectAboutCenter(mStartRevealRect, IconShape.getNormalizationScale());
+            }
+
+            float aspectRatio = mLauncher.getDeviceProfile().aspectRatio;
+            if (mIsVerticalBarLayout) {
+                lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
+            } else {
+                lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
+            }
+
+            int left = mIsRtl
+                    ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
+                    : lp.leftMargin;
+            layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
+
+            float scale = Math.max((float) lp.height / originalHeight,
+                    (float) lp.width / originalWidth);
+            float bgDrawableStartScale;
+            if (isOpening) {
+                bgDrawableStartScale = 1f;
+                mOutline.set(0, 0, originalWidth, originalHeight);
+            } else {
+                bgDrawableStartScale = scale;
+                mOutline.set(0, 0, lp.width, lp.height);
+            }
+            setBackgroundDrawableBounds(bgDrawableStartScale);
+            mEndRevealRect.set(0, 0, lp.width, lp.height);
+            setOutlineProvider(new ViewOutlineProvider() {
+                @Override
+                public void getOutline(View view, Outline outline) {
+                    outline.setRoundRect(mOutline, mTaskCornerRadius);
+                }
+            });
+            setClipToOutline(true);
+        } else {
+            setBackground(drawable);
+            setClipToOutline(false);
+        }
+
+        invalidate();
+        invalidateOutline();
+    }
+
+    @Override
+    public void setClipPath(Path clipPath) {
+        mClipPath = clipPath;
+        invalidate();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        int count = canvas.save();
+        if (mClipPath != null) {
+            canvas.clipPath(mClipPath);
+        }
+        super.draw(canvas);
+        if (mBackground != null) {
+            mBackground.draw(canvas);
+        }
+        if (mForeground != null) {
+            int count2 = canvas.save();
+            canvas.translate(mFgTransX, mFgTransY);
+            mForeground.draw(canvas);
+            canvas.restoreToCount(count2);
+        }
+        canvas.restoreToCount(count);
+    }
+
+    void recycle() {
+        setBackground(null);
+        mIsAdaptiveIcon = false;
+        mForeground = null;
+        mBackground = null;
+        mClipPath = null;
+        mFinalDrawableBounds.setEmpty();
+        if (mRevealAnimator != null) {
+            mRevealAnimator.cancel();
+        }
+        mRevealAnimator = null;
+        mTaskCornerRadius = 0;
+        mOutline.setEmpty();
+        mFgTransY = 0;
+        mFgSpringX.cancel();
+        mFgTransX = 0;
+        mFgSpringY.cancel();
+    }
+}
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index d41bb86..3e2560f 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -18,8 +18,6 @@
 import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
 import static com.android.launcher3.Utilities.getBadge;
 import static com.android.launcher3.Utilities.getFullDrawable;
-import static com.android.launcher3.Utilities.mapToRange;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -28,17 +26,12 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Outline;
-import android.graphics.Path;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.CancellationSignal;
@@ -46,19 +39,17 @@
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import androidx.annotation.WorkerThread;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.InsettableFrameLayout.LayoutParams;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
@@ -66,7 +57,6 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.shortcuts.DeepShortcutView;
@@ -75,8 +65,8 @@
  * A view that is created to look like another view with the purpose of creating fluid animations.
  */
 @TargetApi(Build.VERSION_CODES.Q)
-public class FloatingIconView extends View implements
-        Animator.AnimatorListener, ClipPathView, OnGlobalLayoutListener {
+public class FloatingIconView extends FrameLayout implements
+        Animator.AnimatorListener, OnGlobalLayoutListener {
 
     private static final String TAG = FloatingIconView.class.getSimpleName();
 
@@ -85,81 +75,34 @@
 
     public static final float SHAPE_PROGRESS_DURATION = 0.10f;
     private static final int FADE_DURATION_MS = 200;
-    private static final Rect sTmpRect = new Rect();
     private static final RectF sTmpRectF = new RectF();
     private static final Object[] sTmpObjArray = new Object[1];
 
-    // We spring the foreground drawable relative to the icon's movement in the DragLayer.
-    // We then use these two factor values to scale the movement of the fg within this view.
-    private static final int FG_TRANS_X_FACTOR = 60;
-    private static final int FG_TRANS_Y_FACTOR = 75;
-
-    private static final FloatPropertyCompat<FloatingIconView> mFgTransYProperty
-            = new FloatPropertyCompat<FloatingIconView>("FloatingViewFgTransY") {
-        @Override
-        public float getValue(FloatingIconView view) {
-            return view.mFgTransY;
-        }
-
-        @Override
-        public void setValue(FloatingIconView view, float transY) {
-            view.mFgTransY = transY;
-            view.invalidate();
-        }
-    };
-
-    private static final FloatPropertyCompat<FloatingIconView> mFgTransXProperty
-            = new FloatPropertyCompat<FloatingIconView>("FloatingViewFgTransX") {
-        @Override
-        public float getValue(FloatingIconView view) {
-            return view.mFgTransX;
-        }
-
-        @Override
-        public void setValue(FloatingIconView view, float transX) {
-            view.mFgTransX = transX;
-            view.invalidate();
-        }
-    };
-
     private Runnable mEndRunnable;
     private CancellationSignal mLoadIconSignal;
 
     private final Launcher mLauncher;
-    private final int mBlurSizeOutline;
     private final boolean mIsRtl;
 
     private boolean mIsVerticalBarLayout = false;
-    private boolean mIsAdaptiveIcon = false;
     private boolean mIsOpening;
 
     private IconLoadResult mIconLoadResult;
 
+    private ClipIconView mClipIconView;
     private @Nullable Drawable mBadge;
-    private @Nullable Drawable mForeground;
-    private @Nullable Drawable mBackground;
+
     private float mRotation;
-    private ValueAnimator mRevealAnimator;
-    private final Rect mStartRevealRect = new Rect();
-    private final Rect mEndRevealRect = new Rect();
-    private Path mClipPath;
-    private float mTaskCornerRadius;
 
     private View mOriginalIcon;
     private RectF mPositionOut;
     private Runnable mOnTargetChangeRunnable;
 
-    private final Rect mOutline = new Rect();
     private final Rect mFinalDrawableBounds = new Rect();
 
     private AnimatorSet mFadeAnimatorSet;
     private ListenerView mListenerView;
 
-    private final SpringAnimation mFgSpringY;
-    private float mFgTransY;
-    private final SpringAnimation mFgSpringX;
-    private float mFgTransX;
-
     public FloatingIconView(Context context) {
         this(context, null);
     }
@@ -171,19 +114,11 @@
     public FloatingIconView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mLauncher = Launcher.getLauncher(context);
-        mBlurSizeOutline = getResources().getDimensionPixelSize(
-                R.dimen.blur_size_medium_outline);
         mIsRtl = Utilities.isRtl(getResources());
         mListenerView = new ListenerView(context, attrs);
-
-        mFgSpringX = new SpringAnimation(this, mFgTransXProperty)
-                .setSpring(new SpringForce()
-                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
-                .setStiffness(SpringForce.STIFFNESS_LOW));
-        mFgSpringY = new SpringAnimation(this, mFgTransYProperty)
-                .setSpring(new SpringForce()
-                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
-                        .setStiffness(SpringForce.STIFFNESS_LOW));
+        mClipIconView = new ClipIconView(context, attrs);
+        addView(mClipIconView);
+        setWillNotDraw(false);
     }
 
     @Override
@@ -212,10 +147,12 @@
             float cornerRadius, boolean isOpening) {
         setAlpha(alpha);
 
-        LayoutParams lp = (LayoutParams) getLayoutParams();
+        InsettableFrameLayout.LayoutParams lp =
+                (InsettableFrameLayout.LayoutParams) getLayoutParams();
+
+        DeviceProfile dp = mLauncher.getDeviceProfile();
         float dX = mIsRtl
-                ? rect.left
-                - (mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width)
+                ? rect.left - (dp.widthPx - lp.getMarginStart() - lp.width)
                 : rect.left - lp.getMarginStart();
         float dY = rect.top - lp.topMargin;
         setTranslationX(dX);
@@ -226,69 +163,15 @@
         float scaleY = rect.height() / minSize;
         float scale = Math.max(1f, Math.min(scaleX, scaleY));
 
+        mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening, scale,
+                minSize, lp);
+
         setPivotX(0);
         setPivotY(0);
         setScaleX(scale);
         setScaleY(scale);
 
-        // shapeRevealProgress = 1 when progress = shapeProgressStart + SHAPE_PROGRESS_DURATION
-        float toMax = isOpening ? 1 / SHAPE_PROGRESS_DURATION : 1f;
-        float shapeRevealProgress = Utilities.boundToRange(mapToRange(
-                Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax,
-                LINEAR), 0, 1);
-
-        if (mIsVerticalBarLayout) {
-            mOutline.right = (int) (rect.width() / scale);
-        } else {
-            mOutline.bottom = (int) (rect.height() / scale);
-        }
-
-        mTaskCornerRadius = cornerRadius / scale;
-        if (mIsAdaptiveIcon) {
-            if (!isOpening && progress >= shapeProgressStart) {
-                if (mRevealAnimator == null) {
-                    mRevealAnimator = (ValueAnimator) IconShape.getShape().createRevealAnimator(
-                            this, mStartRevealRect, mOutline, mTaskCornerRadius, !isOpening);
-                    mRevealAnimator.addListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            mRevealAnimator = null;
-                        }
-                    });
-                    mRevealAnimator.start();
-                    // We pause here so we can set the current fraction ourselves.
-                    mRevealAnimator.pause();
-                }
-                mRevealAnimator.setCurrentFraction(shapeRevealProgress);
-            }
-
-            float drawableScale = (mIsVerticalBarLayout ? mOutline.width() : mOutline.height())
-                    / minSize;
-            setBackgroundDrawableBounds(drawableScale);
-            if (isOpening) {
-                // Center align foreground
-                int height = mFinalDrawableBounds.height();
-                int width = mFinalDrawableBounds.width();
-                int diffY = mIsVerticalBarLayout ? 0
-                        : (int) (((height * drawableScale) - height) / 2);
-                int diffX = mIsVerticalBarLayout ? (int) (((width * drawableScale) - width) / 2)
-                        : 0;
-                sTmpRect.set(mFinalDrawableBounds);
-                sTmpRect.offset(diffX, diffY);
-                mForeground.setBounds(sTmpRect);
-            } else {
-                // Spring the foreground relative to the icon's movement within the DragLayer.
-                int diffX = (int) (dX / mLauncher.getDeviceProfile().availableWidthPx
-                        * FG_TRANS_X_FACTOR);
-                int diffY = (int) (dY / mLauncher.getDeviceProfile().availableHeightPx
-                        * FG_TRANS_Y_FACTOR);
-
-                mFgSpringX.animateToFinalPosition(diffX);
-                mFgSpringY.animateToFinalPosition(diffY);
-            }
-        }
         invalidate();
-        invalidateOutline();
     }
 
     @Override
@@ -300,9 +183,7 @@
             mEndRunnable.run();
         } else {
             // End runnable also ends the reveal animator, so we manually handle it here.
-            if (mRevealAnimator != null) {
-                mRevealAnimator.end();
-            }
+            mClipIconView.endReveal();
         }
     }
 
@@ -314,23 +195,25 @@
      */
     private void matchPositionOf(Launcher launcher, View v, boolean isOpening, RectF positionOut) {
         float rotation = getLocationBoundsForView(launcher, v, isOpening, positionOut);
-        final LayoutParams lp = new LayoutParams(
+        final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams(
                 Math.round(positionOut.width()),
                 Math.round(positionOut.height()));
         updatePosition(rotation, positionOut, lp);
         setLayoutParams(lp);
+
+        mClipIconView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
     }
 
-    private void updatePosition(float rotation, RectF position, LayoutParams lp) {
+    private void updatePosition(float rotation, RectF pos, InsettableFrameLayout.LayoutParams lp) {
         mRotation = rotation;
-        mPositionOut.set(position);
+        mPositionOut.set(pos);
         lp.ignoreInsets = true;
         // Position the floating view exactly on top of the original
-        lp.topMargin = Math.round(position.top);
+        lp.topMargin = Math.round(pos.top);
         if (mIsRtl) {
-            lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - position.right));
+            lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - pos.right));
         } else {
-            lp.setMarginStart(Math.round(position.left));
+            lp.setMarginStart(Math.round(pos.left));
         }
         // Set the properties here already to make sure they are available when running the first
         // animation frame.
@@ -449,97 +332,42 @@
     /**
      * Sets the drawables of the {@param originalView} onto this view.
      *
-     * @param originalView The View that the FloatingIconView will replace.
      * @param drawable The drawable of the original view.
      * @param badge The badge of the original view.
      * @param iconOffset The amount of offset needed to match this view with the original view.
      */
     @UiThread
-    private void setIcon(View originalView, @Nullable Drawable drawable, @Nullable Drawable badge,
-            int iconOffset) {
+    private void setIcon(@Nullable Drawable drawable, @Nullable Drawable badge, int iconOffset) {
+        final InsettableFrameLayout.LayoutParams lp =
+                (InsettableFrameLayout.LayoutParams) getLayoutParams();
         mBadge = badge;
-
-        mIsAdaptiveIcon = drawable instanceof AdaptiveIconDrawable;
-        if (mIsAdaptiveIcon) {
-            boolean isFolderIcon = drawable instanceof FolderAdaptiveIcon;
-
-            AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) drawable;
-            Drawable background = adaptiveIcon.getBackground();
-            if (background == null) {
-                background = new ColorDrawable(Color.TRANSPARENT);
-            }
-            mBackground = background;
-            Drawable foreground = adaptiveIcon.getForeground();
-            if (foreground == null) {
-                foreground = new ColorDrawable(Color.TRANSPARENT);
-            }
-            mForeground = foreground;
-
-            final LayoutParams lp = (LayoutParams) getLayoutParams();
+        mClipIconView.setIcon(drawable, iconOffset, lp, mIsOpening);
+        if (drawable instanceof AdaptiveIconDrawable) {
             final int originalHeight = lp.height;
             final int originalWidth = lp.width;
 
-            int blurMargin = mBlurSizeOutline / 2;
             mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight);
 
-            if (!isFolderIcon) {
-                mFinalDrawableBounds.inset(iconOffset - blurMargin, iconOffset - blurMargin);
-            }
-            mForeground.setBounds(mFinalDrawableBounds);
-            mBackground.setBounds(mFinalDrawableBounds);
-
-            mStartRevealRect.set(0, 0, originalWidth, originalHeight);
-
-            if (mBadge != null) {
-                mBadge.setBounds(mStartRevealRect);
-                if (!mIsOpening && !isFolderIcon) {
-                    DRAWABLE_ALPHA.set(mBadge, 0);
-                }
-            }
-
-            if (!isFolderIcon) {
-                Utilities.scaleRectAboutCenter(mStartRevealRect,
-                        IconShape.getNormalizationScale());
-            }
-
             float aspectRatio = mLauncher.getDeviceProfile().aspectRatio;
             if (mIsVerticalBarLayout) {
                 lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
             } else {
                 lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
             }
+            setLayoutParams(lp);
 
-            int left = mIsRtl
-                    ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
-                    : lp.leftMargin;
-            layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
+            final LayoutParams clipViewLp = (LayoutParams) mClipIconView.getLayoutParams();
+            final int clipViewOgHeight = clipViewLp.height;
+            final int clipViewOgWidth = clipViewLp.width;
+            clipViewLp.width = lp.width;
+            clipViewLp.height = lp.height;
+            mClipIconView.setLayoutParams(clipViewLp);
 
-            float scale = Math.max((float) lp.height / originalHeight,
-                    (float) lp.width / originalWidth);
-            float bgDrawableStartScale;
-            if (mIsOpening) {
-                bgDrawableStartScale = 1f;
-                mOutline.set(0, 0, originalWidth, originalHeight);
-            } else {
-                bgDrawableStartScale = scale;
-                mOutline.set(0, 0, lp.width, lp.height);
+            if (mBadge != null) {
+                mBadge.setBounds(0, 0, clipViewOgWidth, clipViewOgHeight);
             }
-            setBackgroundDrawableBounds(bgDrawableStartScale);
-            mEndRevealRect.set(0, 0, lp.width, lp.height);
-            setOutlineProvider(new ViewOutlineProvider() {
-                @Override
-                public void getOutline(View view, Outline outline) {
-                    outline.setRoundRect(mOutline, mTaskCornerRadius);
-                }
-            });
-            setClipToOutline(true);
-        } else {
-            setBackground(drawable);
-            setClipToOutline(false);
         }
-
         invalidate();
-        invalidateOutline();
     }
 
     /**
@@ -556,7 +384,7 @@
 
         synchronized (mIconLoadResult) {
             if (mIconLoadResult.isIconLoaded) {
-                setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
+                setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
                         mIconLoadResult.iconOffset);
                 hideOriginalView(originalView);
             } else {
@@ -565,7 +393,7 @@
                         return;
                     }
 
-                    setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
+                    setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
                             mIconLoadResult.iconOffset);
 
                     setVisibility(VISIBLE);
@@ -585,18 +413,6 @@
         }
     }
 
-    private void setBackgroundDrawableBounds(float scale) {
-        sTmpRect.set(mFinalDrawableBounds);
-        Utilities.scaleRectAboutCenter(sTmpRect, scale);
-        // Since the drawable is at the top of the view, we need to offset to keep it centered.
-        if (mIsVerticalBarLayout) {
-            sTmpRect.offsetTo((int) (mFinalDrawableBounds.left * scale), sTmpRect.top);
-        } else {
-            sTmpRect.offsetTo(sTmpRect.left, (int) (mFinalDrawableBounds.top * scale));
-        }
-        mBackground.setBounds(sTmpRect);
-    }
-
     @WorkerThread
     @SuppressWarnings("WrongThread")
     private static int getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position) {
@@ -626,29 +442,11 @@
     }
 
     @Override
-    public void setClipPath(Path clipPath) {
-        mClipPath = clipPath;
-        invalidate();
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
+    protected void dispatchDraw(Canvas canvas) {
         int count = canvas.save();
         canvas.rotate(mRotation,
                 mFinalDrawableBounds.exactCenterX(), mFinalDrawableBounds.exactCenterY());
-        if (mClipPath != null) {
-            canvas.clipPath(mClipPath);
-        }
-        super.draw(canvas);
-        if (mBackground != null) {
-            mBackground.draw(canvas);
-        }
-        if (mForeground != null) {
-            int count2 = canvas.save();
-            canvas.translate(mFgTransX, mFgTransY);
-            mForeground.draw(canvas);
-            canvas.restoreToCount(count2);
-        }
+        super.dispatchDraw(canvas);
         if (mBadge != null) {
             mBadge.draw(canvas);
         }
@@ -692,7 +490,8 @@
             float rotation = getLocationBoundsForView(mLauncher, mOriginalIcon, mIsOpening,
                     sTmpRectF);
             if (rotation != mRotation || !sTmpRectF.equals(mPositionOut)) {
-                updatePosition(rotation, sTmpRectF, (LayoutParams) getLayoutParams());
+                updatePosition(rotation, sTmpRectF,
+                        (InsettableFrameLayout.LayoutParams) getLayoutParams());
                 if (mOnTargetChangeRunnable != null) {
                     mOnTargetChangeRunnable.run();
                 }
@@ -808,12 +607,6 @@
             }
         });
 
-        if (mBadge != null) {
-            ObjectAnimator badgeFade = ObjectAnimator.ofInt(mBadge, DRAWABLE_ALPHA, 255);
-            badgeFade.addUpdateListener(valueAnimator -> invalidate());
-            fade.play(badgeFade);
-        }
-
         if (originalView instanceof IconLabelDotView) {
             IconLabelDotView view = (IconLabelDotView) originalView;
             fade.addListener(new AnimatorListenerAdapter() {
@@ -855,21 +648,12 @@
         setScaleX(1);
         setScaleY(1);
         setAlpha(1);
-        setBackground(null);
         if (mLoadIconSignal != null) {
             mLoadIconSignal.cancel();
         }
         mLoadIconSignal = null;
         mEndRunnable = null;
-        mIsAdaptiveIcon = false;
-        mForeground = null;
-        mBackground = null;
-        mClipPath = null;
         mFinalDrawableBounds.setEmpty();
-        if (mRevealAnimator != null) {
-            mRevealAnimator.cancel();
-        }
-        mRevealAnimator = null;
         if (mFadeAnimatorSet != null) {
             mFadeAnimatorSet.cancel();
         }
@@ -878,15 +662,10 @@
         mListenerView.setListener(null);
         mOriginalIcon = null;
         mOnTargetChangeRunnable = null;
-        mTaskCornerRadius = 0;
-        mOutline.setEmpty();
-        mFgTransY = 0;
-        mFgSpringX.cancel();
-        mFgTransX = 0;
-        mFgSpringY.cancel();
         mBadge = null;
         sTmpObjArray[0] = null;
         mIconLoadResult = null;
+        mClipIconView.recycle();
     }
 
     private static class IconLoadResult {
@@ -897,7 +676,7 @@
         Runnable onIconLoaded;
         boolean isIconLoaded;
 
-        public IconLoadResult(ItemInfo itemInfo) {
+        IconLoadResult(ItemInfo itemInfo) {
             this.itemInfo = itemInfo;
         }
     }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java b/src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java
deleted file mode 100644
index 7ad85e2..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2020 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.util.FloatProperty;
-import android.view.View;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.states.StateAnimationConfig;
-
-/**
- * Controls blur and wallpaper zoom, for the Launcher surface only.
- */
-public class DepthController implements LauncherStateManager.StateHandler {
-
-    public static final FloatProperty<DepthController> DEPTH =
-            new FloatProperty<DepthController>("depth") {
-                @Override
-                public void setValue(DepthController depthController, float depth) {}
-
-                @Override
-                public Float get(DepthController depthController) {
-                    return 0f;
-                }
-            };
-
-    public DepthController(Launcher l) {}
-
-    public void setSurfaceToLauncher(View v) {}
-
-    @Override
-    public void setState(LauncherState toState) {}
-
-    @Override
-    public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
-            PendingAnimation animation) { }
-}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index f8bbf21..de1ada4 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -314,7 +314,7 @@
                 switchToAllApps();
         allApps.freeze();
         try {
-            allApps.getAppIcon(APP_NAME).dragToWorkspace();
+            allApps.getAppIcon(APP_NAME).dragToWorkspace(false);
             mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName());
         } finally {
             allApps.unfreeze();
@@ -342,7 +342,7 @@
                     getMenuItem(0);
             final String shortcutName = menuItem.getText();
 
-            menuItem.dragToWorkspace();
+            menuItem.dragToWorkspace(false);
             mLauncher.getWorkspace().getWorkspaceAppIcon(shortcutName).launch(getAppPackageName());
         } finally {
             allApps.unfreeze();
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index de9757f..d93915c 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -94,7 +94,7 @@
         WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
         widgets.
                 getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager())).
-                dragToWorkspace();
+                dragToWorkspace(true);
         // Widget id for which the config activity was opened
         mWidgetId = monitor.getWidgetId();
 
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index f9d1d93..788e041 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -57,7 +57,7 @@
                 getWorkspace().
                 openAllWidgets().
                 getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager())).
-                dragToWorkspace();
+                dragToWorkspace(false);
 
         assertTrue(mActivityMonitor.itemExists(
                 (info, view) -> info instanceof LauncherAppWidgetInfo &&
@@ -83,7 +83,7 @@
         mDevice.pressHome();
         mLauncher.getWorkspace().openAllWidgets()
                 .getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity")
-                .dragToWorkspace();
+                .dragToWorkspace(false);
         mLauncher.getWorkspace().getWorkspaceAppIcon("Shortcut")
                 .launch(getAppPackageName());
     }
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 793af48..a3c70ec 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -45,6 +45,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.tapl.Widget;
 import com.android.launcher3.tapl.Workspace;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
@@ -267,8 +268,10 @@
     }
 
     private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
+        final Widget widget = mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT);
+        if (widget == null) mLauncher.dumpViewHierarchy(); // b/152645831
         assertTrue("Widget is not present",
-                mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
+                widget != null);
     }
 
     private void verifyPendingWidgetPresent() {
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 2acab97..80b8e89 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -25,7 +25,6 @@
 import android.view.MotionEvent;
 
 import androidx.annotation.NonNull;
-import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiObject2;
 
 import com.android.launcher3.testing.TestProtocol;
@@ -72,6 +71,7 @@
     }
 
     protected void goToOverviewUnchecked() {
+        final boolean launcherWasVisible = mLauncher.isLauncherVisible();
         switch (mLauncher.getNavigationModel()) {
             case ZERO_BUTTON: {
                 final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
@@ -137,6 +137,15 @@
                         OVERVIEW_STATE_ORDINAL);
                 break;
         }
+        expectSwitchToOverviewEvents();
+
+        if (!launcherWasVisible) {
+            mLauncher.expectEvent(
+                    TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_START_ACTIVITY);
+        }
+    }
+
+    private void expectSwitchToOverviewEvents() {
     }
 
     /**
@@ -157,6 +166,7 @@
     }
 
     protected void quickSwitchToPreviousApp(int expectedState) {
+        final boolean launcherWasVisible = mLauncher.isLauncherVisible();
         boolean transposeInLandscape = false;
         switch (mLauncher.getNavigationModel()) {
             case TWO_BUTTON:
@@ -180,15 +190,17 @@
                     endX = startX;
                     endY = 0;
                 }
-                final boolean launcherIsVisible =
-                        mLauncher.hasLauncherObject(By.textStartsWith(""));
                 final boolean isZeroButton = mLauncher.getNavigationModel()
                         == LauncherInstrumentation.NavigationModel.ZERO_BUTTON;
+                if (!launcherWasVisible) {
+                    mLauncher.expectEvent(
+                            TestProtocol.SEQUENCE_MAIN,
+                            LauncherInstrumentation.EVENT_START_ACTIVITY);
+                }
                 mLauncher.swipeToState(startX, startY, endX, endY, 20, expectedState,
-                        launcherIsVisible && isZeroButton
+                        launcherWasVisible && isZeroButton
                                 ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
-                                : LauncherInstrumentation.GestureScope.OUTSIDE
-                );
+                                : LauncherInstrumentation.GestureScope.OUTSIDE);
                 break;
             }
 
@@ -196,6 +208,11 @@
                 // Double press the recents button.
                 UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
                 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
+                if (!launcherWasVisible) {
+                    mLauncher.expectEvent(
+                            TestProtocol.SEQUENCE_MAIN,
+                            LauncherInstrumentation.EVENT_START_ACTIVITY);
+                }
                 mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL);
                 mLauncher.getOverview();
                 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
@@ -203,6 +220,8 @@
                 break;
         }
         mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
+        mLauncher.expectEvent(
+                TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY);
     }
 
     protected String getSwipeHeightRequestName() {
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index b20384e..d1a1254 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -25,6 +25,8 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import com.android.launcher3.testing.TestProtocol;
+
 /**
  * Ancestor for AppIcon and AppMenuItem.
  */
@@ -62,6 +64,8 @@
                 event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
                 () -> "Launching an app didn't open a new window: " + mObject.getText());
         expectActivityStartEvents();
+        mLauncher.expectEvent(
+                TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY);
 
         mLauncher.assertTrue(
                 "App didn't start: " + selector,
@@ -72,8 +76,9 @@
 
     /**
      * Drags an object to the center of homescreen.
+     * @param startsActivity whether it's expected to start an activity.
      */
-    public void dragToWorkspace() {
+    public void dragToWorkspace(boolean startsActivity) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             final Point launchableCenter = getObject().getVisibleCenter();
             final Point displaySize = mLauncher.getRealDisplaySize();
@@ -86,7 +91,8 @@
                                     ? launchableCenter.x - width / 2
                                     : launchableCenter.x + width / 2,
                             displaySize.y / 2),
-                    getLongPressIndicator());
+                    getLongPressIndicator(),
+                    startsActivity);
         }
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index d894843..d171a69 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -97,6 +97,8 @@
     private static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
     private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPattern("ACTION_CANCEL");
     private static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers");
+    static final Pattern EVENT_START_ACTIVITY = Pattern.compile("Activity\\.onStart");
+    static final Pattern EVENT_STOP_ACTIVITY = Pattern.compile("Activity\\.onStop");
 
     static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
     static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
@@ -316,7 +318,7 @@
         };
     }
 
-    private void dumpViewHierarchy() {
+    public void dumpViewHierarchy() {
         final ByteArrayOutputStream stream = new ByteArrayOutputStream();
         try {
             mDevice.dumpWindowHierarchy(stream);
@@ -637,6 +639,7 @@
             // otherwise waitForIdle may return immediately in case when there was a big enough
             // pause in accessibility events prior to pressing Home.
             final String action;
+            final boolean launcherWasVisible = isLauncherVisible();
             if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
                 checkForAnomaly();
 
@@ -665,12 +668,18 @@
                                 displaySize.x / 2, displaySize.y - 1,
                                 displaySize.x / 2, 0,
                                 ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL,
-                                hasLauncherObject(By.textStartsWith(""))
+                                launcherWasVisible
                                         ? GestureScope.INSIDE_TO_OUTSIDE
                                         : GestureScope.OUTSIDE);
                     }
+                    if (!launcherWasVisible) {
+                        expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_START_ACTIVITY);
+                    }
                 }
             } else {
+                if (!launcherWasVisible) {
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_START_ACTIVITY);
+                }
                 log("Hierarchy before clicking home:");
                 dumpViewHierarchy();
                 log(action = "clicking home button from " + getVisibleStateMessage());
@@ -681,6 +690,7 @@
                         expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
                         expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
                     }
+
                     runToState(
                             waitForSystemUiObject("home")::click,
                             NORMAL_STATE_ORDINAL,
@@ -697,6 +707,11 @@
         }
     }
 
+    boolean isLauncherVisible() {
+        mDevice.waitForIdle();
+        return hasLauncherObject(By.textStartsWith(""));
+    }
+
     /**
      * Gets the Workspace object if the current state is "active home", i.e. workspace. Fails if the
      * launcher is not in that state.
@@ -1116,7 +1131,7 @@
                 break;
             case MotionEvent.ACTION_UP:
                 if (notLauncher3 && gestureScope != GestureScope.INSIDE) {
-                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_PILFER_POINTERS);
+                    expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS);
                 }
                 if (gestureScope != GestureScope.OUTSIDE) {
                     expectEvent(TestProtocol.SEQUENCE_MAIN, gestureScope == GestureScope.INSIDE
diff --git a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
index c2f701b..b8e6c0e 100644
--- a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
@@ -15,11 +15,15 @@
  */
 package com.android.launcher3.tapl;
 
+import android.os.Build;
+
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import com.android.launcher3.testing.TestProtocol;
+
 public class OptionsPopupMenuItem {
 
     private final LauncherInstrumentation mLauncher;
@@ -39,6 +43,12 @@
             LauncherInstrumentation.log("OptionsPopupMenuItem before click "
                     + mObject.getVisibleCenter() + " in " + mObject.getVisibleBounds());
             mLauncher.clickLauncherObject(mObject);
+            if (!Build.MODEL.contains("Cuttlefish") ||
+                    Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q &&
+                            !"R".equals(Build.VERSION.CODENAME)) {
+                mLauncher.expectEvent(
+                        TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY);
+            }
             mLauncher.assertTrue(
                     "App didn't start: " + By.pkg(expectedPackageName),
                     mLauncher.getDevice().wait(Until.hasObject(By.pkg(expectedPackageName)),
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index f955cf2..5c51782 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -79,6 +79,8 @@
                         () -> "Launching task didn't open a new window: "
                                 + mTask.getParent().getContentDescription());
                 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
+                mLauncher.expectEvent(
+                        TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY);
             }
             return new Background(mLauncher);
         }
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 084138c..a14d2f0 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -88,47 +88,50 @@
     }
 
     public Widget getWidget(String labelText) {
-        final UiObject2 widgetsContainer = verifyActiveContainer();
-        final Point displaySize = mLauncher.getRealDisplaySize();
-        final BySelector labelSelector = By.clazz("android.widget.TextView").text(labelText);
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "getting widget " + labelText + " in widgets list")) {
+            final UiObject2 widgetsContainer = verifyActiveContainer();
+            final Point displaySize = mLauncher.getRealDisplaySize();
+            final BySelector labelSelector = By.clazz("android.widget.TextView").text(labelText);
 
-        int i = 0;
-        for (; ; ) {
-            final Collection<UiObject2> cells = mLauncher.getObjectsInContainer(
-                    widgetsContainer, "widgets_scroll_container");
-            mLauncher.assertTrue("Widgets doesn't have 2 rows", cells.size() >= 2);
-            for (UiObject2 cell : cells) {
-                final UiObject2 label = cell.findObject(labelSelector);
-                if (label == null) continue;
+            int i = 0;
+            for (; ; ) {
+                final Collection<UiObject2> cells = mLauncher.getObjectsInContainer(
+                        widgetsContainer, "widgets_scroll_container");
+                mLauncher.assertTrue("Widgets doesn't have 2 rows", cells.size() >= 2);
+                for (UiObject2 cell : cells) {
+                    final UiObject2 label = cell.findObject(labelSelector);
+                    if (label == null) continue;
 
-                final UiObject2 widget = label.getParent().getParent();
-                mLauncher.assertEquals(
-                        "View is not WidgetCell",
-                        "com.android.launcher3.widget.WidgetCell",
-                        widget.getClassName());
+                    final UiObject2 widget = label.getParent().getParent();
+                    mLauncher.assertEquals(
+                            "View is not WidgetCell",
+                            "com.android.launcher3.widget.WidgetCell",
+                            widget.getClassName());
 
-                int maxWidth = 0;
-                for (UiObject2 sibling : widget.getParent().getChildren()) {
-                    maxWidth = Math.max(sibling.getVisibleBounds().width(), maxWidth);
+                    int maxWidth = 0;
+                    for (UiObject2 sibling : widget.getParent().getChildren()) {
+                        maxWidth = Math.max(sibling.getVisibleBounds().width(), maxWidth);
+                    }
+
+                    int visibleDelta = maxWidth - widget.getVisibleBounds().width();
+                    if (visibleDelta > 0) {
+                        Rect parentBounds = cell.getVisibleBounds();
+                        mLauncher.linearGesture(parentBounds.centerX() + visibleDelta
+                                        + mLauncher.getTouchSlop(),
+                                parentBounds.centerY(), parentBounds.centerX(),
+                                parentBounds.centerY(), 10, true, GestureScope.INSIDE);
+                    }
+
+                    if (widget.getVisibleBounds().bottom
+                            <= displaySize.y - mLauncher.getBottomGestureSize()) {
+                        return new Widget(mLauncher, widget);
+                    }
                 }
 
-                int visibleDelta = maxWidth - widget.getVisibleBounds().width();
-                if (visibleDelta > 0) {
-                    Rect parentBounds = cell.getVisibleBounds();
-                    mLauncher.linearGesture(parentBounds.centerX() + visibleDelta
-                                    + mLauncher.getTouchSlop(),
-                            parentBounds.centerY(), parentBounds.centerX(),
-                            parentBounds.centerY(), 10, true, GestureScope.INSIDE);
-                }
-
-                if (widget.getVisibleBounds().bottom
-                        <= displaySize.y - mLauncher.getBottomGestureSize()) {
-                    return new Widget(mLauncher, widget);
-                }
+                mLauncher.assertTrue("Too many attempts", ++i <= 40);
+                mLauncher.scrollToLastVisibleRow(widgetsContainer, cells, 0);
             }
-
-            mLauncher.assertTrue("Too many attempts", ++i <= 40);
-            mLauncher.scrollToLastVisibleRow(widgetsContainer, cells, 0);
         }
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 3f5dc8d..9ef6476 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -177,7 +177,8 @@
                             getHotseatAppIcon("Chrome"),
                             new Point(mLauncher.getDevice().getDisplayWidth(),
                                     workspace.getVisibleBounds().centerY()),
-                            "deep_shortcuts_container");
+                            "deep_shortcuts_container",
+                            false);
                     verifyActiveContainer();
                 }
             }
@@ -198,7 +199,7 @@
 
     static void dragIconToWorkspace(
             LauncherInstrumentation launcher, Launchable launchable, Point dest,
-            String longPressIndicator) {
+            String longPressIndicator, boolean startsActivity) {
         LauncherInstrumentation.log("dragIconToWorkspace: begin");
         final Point launchableCenter = launchable.getObject().getVisibleCenter();
         final long downTime = SystemClock.uptimeMillis();
@@ -219,6 +220,10 @@
                         downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest,
                         LauncherInstrumentation.GestureScope.INSIDE),
                 NORMAL_STATE_ORDINAL);
+        if (startsActivity) {
+            launcher.expectEvent(
+                    TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY);
+        }
         LauncherInstrumentation.log("dragIconToWorkspace: end");
         launcher.waitUntilGone("drop_target_bar");
     }
@@ -281,16 +286,22 @@
 
     @Nullable
     public Widget tryGetWidget(String label, long timeout) {
-        final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
-                By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView").desc(label),
-                timeout);
-        return widget != null ? new Widget(mLauncher, widget) : null;
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "getting widget " + label + " on workspace with timeout " + timeout)) {
+            final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
+                    By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView").desc(label),
+                    timeout);
+            return widget != null ? new Widget(mLauncher, widget) : null;
+        }
     }
 
     @Nullable
     public Widget tryGetPendingWidget(long timeout) {
-        final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
-                By.clazz("com.android.launcher3.widget.PendingAppWidgetHostView"), timeout);
-        return widget != null ? new Widget(mLauncher, widget) : null;
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "getting pending widget on workspace with timeout " + timeout)) {
+            final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
+                    By.clazz("com.android.launcher3.widget.PendingAppWidgetHostView"), timeout);
+            return widget != null ? new Widget(mLauncher, widget) : null;
+        }
     }
 }
\ No newline at end of file