Merge "Allow quick switch from 3P launcher home in 2 button mode" into ub-launcher3-master
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
index 0254340..f7e71f3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
@@ -32,6 +32,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.allapps.AllAppsStore;
@@ -45,11 +46,13 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.ComponentKey;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.IntStream;
 
 /**
  * Provides prediction ability for the hotseat. Fills gaps in hotseat with predicted items, allows
@@ -73,6 +76,7 @@
 
     private DropTarget.DragObject mDragObject;
     private int mHotSeatItemsCount;
+    private int mPredictedSpotsCount = 0;
 
     private Launcher mLauncher;
     private Hotseat mHotseat;
@@ -86,6 +90,8 @@
 
     private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
 
+    private static HotseatPredictionController sInstance;
+
     public HotseatPredictionController(Launcher launcher) {
         mLauncher = launcher;
         mHotseat = launcher.getHotseat();
@@ -95,6 +101,7 @@
         mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
         launcher.getDeviceProfile().inv.addOnChangeListener(this);
         mHotseat.addOnAttachStateChangeListener(this);
+        sInstance = this;
     }
 
     @Override
@@ -144,6 +151,7 @@
             }
             preparePredictionInfo(predictedItem, rank);
         }
+        mPredictedSpotsCount = predictionIndex;
         bindItems(newItems, animate, callback);
     }
 
@@ -421,6 +429,26 @@
         }
     }
 
+    /**
+     * Fill in predicted_rank field based on app prediction.
+     * Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT
+     */
+    public static void fillInHybridHotseatRank(
+            @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
+        if (sInstance == null || itemInfo.getTargetComponent() == null
+                || itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+            return;
+        }
+        final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
+
+        final List<ComponentKeyMapper> predictedApps = sInstance.mComponentKeyMappers;
+        IntStream.range(0, predictedApps.size())
+                .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
+                .findFirst()
+                .ifPresent((rank) -> target.predictedRank =
+                        Integer.parseInt(sInstance.mPredictedSpotsCount + "0" + rank));
+    }
+
     private static boolean isPredictedIcon(View view) {
         return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo
                 && ((WorkspaceItemInfo) view.getTag()).container
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index f9ee701..e45eded 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -26,6 +26,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.launcher3.HotseatPredictionController;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
 import com.android.launcher3.ItemInfo;
@@ -316,6 +317,11 @@
                 && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
             return;
         }
+        if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+            HotseatPredictionController.fillInHybridHotseatRank(itemInfo, target);
+            return;
+        }
+
         final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
         final List<ComponentKeyMapper> predictedApps = manager.getCurrentState().apps;
         IntStream.range(0, predictedApps.size())
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index ed5dba1..bd37e56 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.SysUINavigationMode;
@@ -140,6 +141,10 @@
         if (launcher.getDeviceProfile().isVerticalBarLayout()) {
             return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
         } else {
+            if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+                return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
+            }
+
             boolean hasAllAppsHeaderExtra = launcher.getAppsView() != null
                     && launcher.getAppsView().getFloatingHeaderView().hasVisibleContent();
             return HOTSEAT_SEARCH_BOX | VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON |
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index b14da5c..630dd70 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -430,20 +430,6 @@
         final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
         anim.addOnUpdateListener(new RectFSpringAnim.OnUpdateListener() {
 
-            // Alpha interpolates between [1, 0] between progress values [start, end]
-            final float start = 0f;
-            final float end = 0.85f;
-
-            private float getWindowAlpha(float progress) {
-                if (progress <= start) {
-                    return 1f;
-                }
-                if (progress >= end) {
-                    return 0f;
-                }
-                return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
-            }
-
             @Override
             public void onUpdate(RectF currentRect, float progress) {
                 homeAnim.setPlayFraction(progress);
@@ -484,6 +470,24 @@
         return anim;
     }
 
+    /**
+     * @param progress The progress of the animation to the home screen.
+     * @return The current alpha to set on the animating app window.
+     */
+    protected float getWindowAlpha(float progress) {
+        // Alpha interpolates between [1, 0] between progress values [start, end]
+        final float start = 0f;
+        final float end = 0.85f;
+
+        if (progress <= start) {
+            return 1f;
+        }
+        if (progress >= end) {
+            return 0f;
+        }
+        return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
+    }
+
     public interface Factory {
 
         BaseSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index 714de28..40235d7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -34,6 +36,7 @@
 import android.graphics.RectF;
 import android.os.Bundle;
 import android.util.ArrayMap;
+import android.view.MotionEvent;
 
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimationSuccessListener;
@@ -97,6 +100,7 @@
     private final boolean mContinuingLastGesture;
     private final boolean mRunningOverHome;
     private final boolean mSwipeUpOverHome;
+    private boolean mTouchedHomeDuringTransition;
 
     private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
     private RunningWindowAnim mFinishAnimation;
@@ -105,13 +109,14 @@
             GestureState gestureState, InputConsumerController inputConsumer,
             boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
         super(context, deviceState, gestureState, inputConsumer);
-        mLauncherAlpha.value = 1;
 
         mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
         mContinuingLastGesture = continuingLastGesture;
         mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
         mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode;
 
+        // Keep the home launcher invisible until we decide to land there.
+        mLauncherAlpha.value = mRunningOverHome ? 1 : 0;
         if (mSwipeUpOverHome) {
             mAppWindowAnimationHelper.setBaseAlphaCallback((t, a) -> 1 - mLauncherAlpha.value);
         } else {
@@ -198,15 +203,27 @@
     @Override
     protected InputConsumer createNewInputProxyHandler() {
         // Just consume all input on the active task
-        return InputConsumer.NO_OP;
+        return new InputConsumer() {
+            @Override
+            public int getType() {
+                return InputConsumer.TYPE_NO_OP;
+            }
+
+            @Override
+            public void onMotionEvent(MotionEvent ev) {
+                mTouchedHomeDuringTransition = true;
+            }
+        };
     }
 
     @Override
     public void onMotionPauseChanged(boolean isPaused) {
         if (!mInQuickSwitchMode) {
             mIsMotionPaused = isPaused;
-            mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1)
-                    .setDuration(150).start();
+            if (mSwipeUpOverHome) {
+                mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1)
+                        .setDuration(150).start();
+            }
             performHapticFeedback();
         }
     }
@@ -315,7 +332,14 @@
                     // Send a home intent to clear the task stack
                     mContext.startActivity(mGestureState.getHomeIntent());
                 } else {
-                    mRecentsAnimationController.finish(true, null, true);
+                    mRecentsAnimationController.finish(true, () -> {
+                        if (!mTouchedHomeDuringTransition) {
+                            // If the user hasn't interacted with the screen during the transition,
+                            // send a home intent so launcher can go to the default home screen.
+                            // (If they are trying to touch something, we don't want to interfere.)
+                            mContext.startActivity(mGestureState.getHomeIntent());
+                        }
+                    }, true);
                 }
                 break;
             }
@@ -389,6 +413,8 @@
             };
 
             if (mGestureState.getEndTarget() == HOME && !mRunningOverHome) {
+                mRecentsAnimationController.enableInputProxy(mInputConsumer,
+                        this::createNewInputProxyHandler);
                 RectFSpringAnim anim = createWindowAnimationToHome(mCurrentShift.value, duration);
                 anim.addAnimatorListener(endListener);
                 anim.start(mEndVelocityPxPerMs);
@@ -445,11 +471,18 @@
             @Override
             public AnimatorPlaybackController createActivityAnimationToHome() {
                 AnimatorSet anim = new AnimatorSet();
-                anim.play(mLauncherAlpha.animateToValue(mLauncherAlpha.value, 1));
+                Animator fadeInLauncher = mLauncherAlpha.animateToValue(mLauncherAlpha.value, 1);
+                fadeInLauncher.setInterpolator(ACCEL_2);
+                anim.play(fadeInLauncher);
                 anim.setDuration(duration);
                 return AnimatorPlaybackController.wrap(anim, duration);
             }
         };
         return createWindowAnimationToHome(startProgress, factory);
     }
+
+    @Override
+    protected float getWindowAlpha(float progress) {
+        return 1 - ACCEL_1_5.getInterpolation(progress);
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewActionsFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewActionsFactory.java
new file mode 100644
index 0000000..6d17b27
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewActionsFactory.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 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 com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+/**
+ * Overview actions are shown in overview underneath the task snapshot. This factory class is
+ * overrideable in an overlay. The {@link OverviewActions} class provides the view that should be
+ * shown in the Overview.
+ */
+public class OverviewActionsFactory implements ResourceBasedOverride {
+
+    public static final MainThreadInitializedObject<OverviewActionsFactory> INSTANCE =
+            forOverride(OverviewActionsFactory.class, R.string.overview_actions_factory_class);
+
+    /** Create a new Overview Actions for interacting between the actions and overview. */
+    public OverviewActions createOverviewActions() {
+        return new OverviewActions();
+    }
+
+    /** Overlay overrideable, base class does nothing. */
+    public static class OverviewActions {
+        /** Get the view to show in the overview. */
+        public View getView() {
+            return null;
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index e0e20ee..3f5179f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
+import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -38,6 +39,7 @@
 import com.android.launcher3.LauncherAnimationRunner;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.util.ObjectWrapper;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.fallback.FallbackRecentsView;
@@ -217,6 +219,12 @@
         mFallbackRecentsView.reset();
     }
 
+    @Override
+    protected void onResume() {
+        super.onResume();
+        AccessibilityManagerCompat.sendStateEventToTest(getBaseContext(), OVERVIEW_STATE_ORDINAL);
+    }
+
     public void onTaskLaunched() {
         mFallbackRecentsView.resetTaskVisuals();
     }
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 b602cea..a8d88b9 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
@@ -111,6 +111,7 @@
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.ViewUtils;
 import com.android.quickstep.util.AppWindowAnimationHelper;
+import com.android.quickstep.util.LayoutUtils;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -344,8 +345,7 @@
         setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
         mTaskTopMargin = getResources()
                 .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
-        mTaskBottomMargin = getResources().getDimensionPixelSize(
-                R.dimen.task_thumbnail_bottom_margin);
+        mTaskBottomMargin = LayoutUtils.thumbnailBottomMargin(getResources());
         mSquaredTouchSlop = squaredTouchSlop(context);
 
         mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 0bfde64..94cec72 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -47,11 +47,14 @@
 import android.widget.FrameLayout;
 import android.widget.Toast;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -59,11 +62,13 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.util.ViewPool.Reusable;
+import com.android.quickstep.OverviewActionsFactory;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskIconCache;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
+import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.TaskCornerRadius;
 import com.android.quickstep.views.RecentsView.PageCallbacks;
 import com.android.quickstep.views.RecentsView.ScrollState;
@@ -159,6 +164,9 @@
     private final float mWindowCornerRadius;
     private final BaseDraggingActivity mActivity;
 
+    private OverviewActionsFactory.OverviewActions mOverviewActions;
+    @Nullable private View mActionsView;
+
     private ObjectAnimator mIconAndDimAnimator;
     private float mIconScaleAnimStartProgress = 0;
     private float mFocusTransitionProgress = 1;
@@ -214,6 +222,7 @@
         mCurrentFullscreenParams = new FullscreenDrawParams(mCornerRadius);
         mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
 
+        mOverviewActions = OverviewActionsFactory.INSTANCE.get(context).createOverviewActions();
         mOutlineProvider = new TaskOutlineProvider(getResources(), mCurrentFullscreenParams);
         setOutlineProvider(mOutlineProvider);
     }
@@ -223,6 +232,21 @@
         super.onFinishInflate();
         mSnapshotView = findViewById(R.id.snapshot);
         mIconView = findViewById(R.id.icon);
+
+        TaskView.LayoutParams thumbnailParams = (LayoutParams) mSnapshotView.getLayoutParams();
+        thumbnailParams.bottomMargin = LayoutUtils.thumbnailBottomMargin(getResources());
+        mSnapshotView.setLayoutParams(thumbnailParams);
+
+
+        if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+            mActionsView = mOverviewActions.getView();
+            if (mActionsView != null) {
+                TaskView.LayoutParams params = new TaskView.LayoutParams(LayoutParams.MATCH_PARENT,
+                        getResources().getDimensionPixelSize(R.dimen.overview_actions_height),
+                        Gravity.BOTTOM);
+                addView(mActionsView, params);
+            }
+        }
     }
 
     public TaskMenuView getMenuView() {
@@ -422,6 +446,10 @@
         mIconView.setScaleX(scale);
         mIconView.setScaleY(scale);
 
+        if (mActionsView != null) {
+            mActionsView.setAlpha(scale);
+        }
+
         mFooterVerticalOffset = 1.0f - scale;
         for (FooterWrapper footer : mFooters) {
             if (footer != null) {
@@ -626,7 +654,7 @@
 
         TaskOutlineProvider(Resources res, FullscreenDrawParams fullscreenParams) {
             mMarginTop = res.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
-            mMarginBottom = res.getDimensionPixelSize(R.dimen.task_thumbnail_bottom_margin);
+            mMarginBottom = LayoutUtils.thumbnailBottomMargin(res);
             mFullscreenParams = fullscreenParams;
         }
 
@@ -783,6 +811,7 @@
 
     /**
      * Hides the icon and shows insets when this TaskView is about to be shown fullscreen.
+     *
      * @param progress: 0 = show icon and no insets; 1 = don't show icon and show full insets.
      */
     public void setFullscreenProgress(float progress) {
@@ -793,6 +822,9 @@
         mFullscreenProgress = progress;
         boolean isFullscreen = mFullscreenProgress > 0;
         mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
+        if (mActionsView != null) {
+            mActionsView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
+        }
         setClipChildren(!isFullscreen);
         setClipToPadding(!isFullscreen);
 
@@ -873,4 +905,5 @@
             mScale = scale;
         }
     }
+
 }
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 7a36416..60cfa0c 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -25,8 +25,7 @@
         android:id="@+id/snapshot"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_marginTop="@dimen/task_thumbnail_top_margin"
-        android:layout_marginBottom="@dimen/task_thumbnail_bottom_margin"/>
+        android:layout_marginTop="@dimen/task_thumbnail_top_margin"/>
 
     <com.android.quickstep.views.IconView
         android:id="@+id/icon"
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 327bb14..24ab487 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -15,6 +15,8 @@
 -->
 <resources>
     <string name="task_overlay_factory_class" translatable="false"></string>
+    <!-- Class name for factory object that creates the overview actions UI when enabled. -->
+    <string name="overview_actions_factory_class" translatable="false" />
 
     <!-- Activity which blocks home gesture -->
     <string name="gesture_blocking_activity" translatable="false"></string>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 82833ea..9ff1350 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -18,12 +18,13 @@
 
     <dimen name="task_thumbnail_top_margin">24dp</dimen>
     <dimen name="task_thumbnail_half_top_margin">12dp</dimen>
-    <!-- Can be overridden in overlays. -->
-    <dimen name="task_thumbnail_bottom_margin">0dp</dimen>
     <dimen name="task_thumbnail_icon_size">48dp</dimen>
     <!-- For screens without rounded corners -->
     <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="recents_page_spacing">10dp</dimen>
     <dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
     <dimen name="overview_peek_distance">96dp</dimen>
@@ -58,6 +59,7 @@
     <dimen name="task_card_menu_shadow_height">3dp</dimen>
     <dimen name="task_card_menu_horizontal_padding">0dp</dimen>
     <dimen name="portrait_task_card_horz_space">136dp</dimen>
+    <dimen name="portrait_task_card_horz_space_big_overview">24dp</dimen>
     <dimen name="landscape_task_card_horz_space">200dp</dimen>
     <dimen name="multi_window_task_card_horz_space">100dp</dimen>
     <!-- Copied from framework resource:
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index 5aa4388..c86139b 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -229,6 +229,31 @@
             params.putInt(EXTRA_MAX_NUM_ACTIONS_SHOWN, 1);
             // Perform wellbeing call .
             remoteActionBundle = client.call(METHOD_GET_ACTIONS, null, params);
+
+            synchronized (mModelLock) {
+                // Remove the entries for requested packages, and then update the fist with what we
+                // got from service
+                Arrays.stream(packageNames).forEach(mPackageToActionId::remove);
+
+                // The result consists of sub-bundles, each one is per a remote action. Each
+                // sub-bundle has a RemoteAction and a list of packages to which the action applies.
+                for (String actionId :
+                        remoteActionBundle.getStringArray(EXTRA_ACTIONS)) {
+                    final Bundle actionBundle = remoteActionBundle.getBundle(actionId);
+                    mActionIdMap.put(actionId,
+                            actionBundle.getParcelable(EXTRA_ACTION));
+
+                    final String[] packagesForAction =
+                            actionBundle.getStringArray(EXTRA_PACKAGES);
+                    if (DEBUG || mIsInTest) {
+                        Log.d(TAG, "....actionId: " + actionId + ", packages: " + String.join(", ",
+                                packagesForAction));
+                    }
+                    for (String packageName : packagesForAction) {
+                        mPackageToActionId.put(packageName, actionId);
+                    }
+                }
+            }
         } catch (DeadObjectException e) {
             Log.i(TAG, "retrieveActions(): DeadObjectException");
             return false;
@@ -237,31 +262,6 @@
             if (mIsInTest) throw new RuntimeException(e);
             return true;
         }
-
-        synchronized (mModelLock) {
-            // Remove the entries for requested packages, and then update the fist with what we
-            // got from service
-            Arrays.stream(packageNames).forEach(mPackageToActionId::remove);
-
-            // The result consists of sub-bundles, each one is per a remote action. Each sub-bundle
-            // has a RemoteAction and a list of packages to which the action applies.
-            for (String actionId :
-                    remoteActionBundle.getStringArray(EXTRA_ACTIONS)) {
-                final Bundle actionBundle = remoteActionBundle.getBundle(actionId);
-                mActionIdMap.put(actionId,
-                        actionBundle.getParcelable(EXTRA_ACTION));
-
-                final String[] packagesForAction =
-                        actionBundle.getStringArray(EXTRA_PACKAGES);
-                if (DEBUG || mIsInTest) {
-                    Log.d(TAG, "....actionId: " + actionId + ", packages: " + String.join(", ",
-                            packagesForAction));
-                }
-                for (String packageName : packagesForAction) {
-                    mPackageToActionId.put(packageName, actionId);
-                }
-            }
-        }
         if (DEBUG || mIsInTest) Log.i(TAG, "retrieveActions(): finished");
         return true;
     }
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index c47bb4a..d49ff89 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -26,6 +26,7 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.quickstep.SysUINavigationMode;
 
 import java.lang.annotation.Retention;
@@ -57,9 +58,16 @@
         } else {
             Resources res = context.getResources();
 
-            extraSpace = getDefaultSwipeHeight(context, dp) + dp.verticalDragHandleSizePx
-                    + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
-                    + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
+            if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+                //TODO: this needs to account for the swipe gesture height and accessibility
+                // UI when shown.
+                extraSpace = 0;
+            } else {
+                extraSpace = getDefaultSwipeHeight(context, dp) + dp.verticalDragHandleSizePx
+                        + res.getDimensionPixelSize(
+                                R.dimen.dynamic_grid_hotseat_extra_vertical_size)
+                        + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
+            }
         }
         calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect);
     }
@@ -99,13 +107,20 @@
         } else {
             taskWidth = dp.availableWidthPx;
             taskHeight = dp.availableHeightPx;
-            paddingHorz = res.getDimension(dp.isVerticalBarLayout()
-                    ? R.dimen.landscape_task_card_horz_space
-                    : R.dimen.portrait_task_card_horz_space);
+
+            final int paddingResId;
+            if (dp.isVerticalBarLayout()) {
+                paddingResId = R.dimen.landscape_task_card_horz_space;
+            } else if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+                paddingResId = R.dimen.portrait_task_card_horz_space_big_overview;
+            } else {
+                paddingResId = R.dimen.portrait_task_card_horz_space;
+            }
+            paddingHorz = res.getDimension(paddingResId);
         }
 
-        float topIconMargin = res.getDimension(R.dimen.task_thumbnail_top_margin);
-        float bottomMargin = res.getDimension(R.dimen.task_thumbnail_bottom_margin);
+        float topIconMargin =   res.getDimension(R.dimen.task_thumbnail_top_margin);
+        float bottomMargin = thumbnailBottomMargin(res);
         float paddingVert = res.getDimension(R.dimen.task_card_vert_space);
 
         // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
@@ -136,4 +151,16 @@
                 R.dimen.task_card_vert_space);
         return shelfHeight + spaceBetweenShelfAndRecents;
     }
+
+    /**
+     * Get the margin that the task thumbnail view should use.
+     * @return the margin in pixels.
+     */
+    public static int thumbnailBottomMargin(Resources resources) {
+        if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+            return resources.getDimensionPixelSize(R.dimen.overview_actions_height);
+        } else {
+            return 0;
+        }
+    }
 }
diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
index 62915f2..310d43c 100644
--- a/robolectric_tests/Android.mk
+++ b/robolectric_tests/Android.mk
@@ -27,7 +27,7 @@
     mockito-robolectric-prebuilt \
     truth-prebuilt
 LOCAL_JAVA_LIBRARIES := \
-    platform-robolectric-3.6.1-prebuilt
+    platform-robolectric-4.3-prebuilt
 
 LOCAL_JAVA_RESOURCE_DIRS := resources config
 
@@ -54,4 +54,4 @@
 
 LOCAL_ROBOTEST_TIMEOUT := 36000
 
-include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk
+include prebuilts/misc/common/robolectric/4.3/run_robotests.mk
diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index 5b6d94d..f7e05a4 100644
--- a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -1,10 +1,13 @@
 package com.android.launcher3.model;
 
+import static com.android.launcher3.shadows.ShadowLooperExecutor.reinitializeStaticExecutors;
+
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.robolectric.util.ReflectionHelpers.setField;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -29,6 +32,7 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.cache.CachingLogic;
 import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.pm.PackageInstallerCompat;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.TestLauncherProvider;
 
@@ -53,7 +57,7 @@
 public class BaseModelUpdateTaskTestCase {
 
     public final HashMap<Class, HashMap<String, Field>> fieldCache = new HashMap<>();
-    private TestLauncherProvider mProvider;
+    public TestLauncherProvider provider;
 
     public Context targetContext;
     public UserHandle myUser;
@@ -71,9 +75,11 @@
     @Before
     public void setUp() throws Exception {
         ShadowLog.stream = System.out;
+        reinitializeStaticExecutors();
+        setField(PackageInstallerCompat.class, null, "sInstance", null);
 
-        mProvider = Robolectric.setupContentProvider(TestLauncherProvider.class);
-        ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, mProvider);
+        provider = Robolectric.setupContentProvider(TestLauncherProvider.class);
+        ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, provider);
 
         callbacks = mock(Callbacks.class);
         appState = mock(LauncherAppState.class);
diff --git a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
new file mode 100644
index 0000000..9e4a43c
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2019 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.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.robolectric.Shadows.shadowOf;
+import static org.robolectric.util.ReflectionHelpers.setField;
+
+import android.content.ComponentName;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageInstaller.SessionParams;
+import android.net.Uri;
+import android.provider.Settings;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.shadows.LShadowLauncherApps;
+import com.android.launcher3.shadows.LShadowUserManager;
+import com.android.launcher3.shadows.ShadowLooperExecutor;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.LauncherLayoutBuilder;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
+import org.robolectric.shadows.ShadowPackageManager;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Tests for layout parser for remote layout
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {LShadowUserManager.class, LShadowLauncherApps.class, ShadowLooperExecutor.class})
+@LooperMode(Mode.PAUSED)
+public class DefaultLayoutProviderTest extends BaseModelUpdateTaskTestCase {
+
+    private static final String SETTINGS_APP = "com.android.settings";
+    private static final String TEST_PROVIDER_AUTHORITY =
+            DefaultLayoutProviderTest.class.getName().toLowerCase();
+
+    private static final int BITMAP_SIZE = 10;
+    private static final int GRID_SIZE = 4;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        InvariantDeviceProfile.INSTANCE.initializeForTesting(idp);
+        CustomWidgetManager.INSTANCE.initializeForTesting(mock(CustomWidgetManager.class));
+
+        idp.numRows = idp.numColumns = idp.numHotseatIcons = GRID_SIZE;
+        idp.iconBitmapSize = BITMAP_SIZE;
+
+        provider.setAllowLoadDefaultFavorites(true);
+        Settings.Secure.putString(targetContext.getContentResolver(),
+                "launcher3.layout.provider", TEST_PROVIDER_AUTHORITY);
+
+        ShadowPackageManager spm = shadowOf(targetContext.getPackageManager());
+        spm.addProviderIfNotPresent(new ComponentName("com.test", "Dummy")).authority =
+                TEST_PROVIDER_AUTHORITY;
+        spm.addActivityIfNotPresent(new ComponentName(SETTINGS_APP, SETTINGS_APP));
+    }
+
+    @After
+    public void cleanup() {
+        InvariantDeviceProfile.INSTANCE.initializeForTesting(null);
+        CustomWidgetManager.INSTANCE.initializeForTesting(null);
+    }
+
+    @Test
+    public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception {
+        writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0)
+                .putApp(SETTINGS_APP, SETTINGS_APP));
+
+        // Verify one item in hotseat
+        assertEquals(1, bgDataModel.workspaceItems.size());
+        ItemInfo info = bgDataModel.workspaceItems.get(0);
+        assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container);
+        assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPLICATION, info.itemType);
+    }
+
+    @Test
+    public void testCustomProfileLoaded_with_folder() throws Exception {
+        writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy)
+                .addApp(SETTINGS_APP, SETTINGS_APP)
+                .addApp(SETTINGS_APP, SETTINGS_APP)
+                .addApp(SETTINGS_APP, SETTINGS_APP)
+                .build());
+
+        // Verify folder
+        assertEquals(1, bgDataModel.workspaceItems.size());
+        ItemInfo info = bgDataModel.workspaceItems.get(0);
+        assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
+        assertEquals(3, ((FolderInfo) info).contents.size());
+    }
+
+    @Test
+    public void testCustomProfileLoaded_with_widget() throws Exception {
+        String pendingAppPkg = "com.test.pending";
+
+        // Add a dummy session info so that the widget exists
+        SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+        params.setAppPackageName(pendingAppPkg);
+
+        PackageInstaller installer = targetContext.getPackageManager().getPackageInstaller();
+        int sessionId = installer.createSession(params);
+        SessionInfo sessionInfo = installer.getSessionInfo(sessionId);
+        setField(sessionInfo, "installerPackageName", "com.test");
+        setField(sessionInfo, "appIcon", BitmapInfo.LOW_RES_ICON);
+
+        writeLayoutAndLoad(new LauncherLayoutBuilder().atWorkspace(0, 1, 0)
+                .putWidget(pendingAppPkg, "DummyWidget", 2, 2));
+
+        // Verify widget
+        assertEquals(1, bgDataModel.appWidgets.size());
+        ItemInfo info = bgDataModel.appWidgets.get(0);
+        assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET, info.itemType);
+        assertEquals(2, info.spanX);
+        assertEquals(2, info.spanY);
+    }
+
+    private void writeLayoutAndLoad(LauncherLayoutBuilder builder) throws Exception {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        builder.build(new OutputStreamWriter(bos));
+
+        Uri layoutUri = LauncherProvider.getLayoutUri(TEST_PROVIDER_AUTHORITY, targetContext);
+        shadowOf(targetContext.getContentResolver()).registerInputStream(layoutUri,
+                new ByteArrayInputStream(bos.toByteArray()));
+
+        LoaderResults results = new LoaderResults(appState, bgDataModel, allAppsList, 0,
+                new WeakReference<>(callbacks));
+        LoaderTask task = new LoaderTask(appState, allAppsList, bgDataModel, results);
+        Executors.MODEL_EXECUTOR.submit(() -> task.loadWorkspace(new ArrayList<>())).get();
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
new file mode 100644
index 0000000..204ec9b
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 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.shadows;
+
+import static org.robolectric.util.ReflectionHelpers.ClassParameter;
+import static org.robolectric.util.ReflectionHelpers.callConstructor;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.ArraySet;
+
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowLauncherApps;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Extension of {@link ShadowLauncherApps} with missing shadow methods
+ */
+@Implements(value = LauncherApps.class)
+public class LShadowLauncherApps extends ShadowLauncherApps {
+
+    public final ArraySet<PackageUserKey> disabledApps = new ArraySet<>();
+    public final ArraySet<ComponentKey> disabledActivities = new ArraySet<>();
+
+    @Implementation
+    @Override
+    protected List<ShortcutInfo> getShortcuts(LauncherApps.ShortcutQuery query, UserHandle user) {
+        try {
+            return super.getShortcuts(query, user);
+        } catch (UnsupportedOperationException e) {
+            return Collections.emptyList();
+        }
+    }
+
+    @Implementation
+    protected boolean isPackageEnabled(String packageName, UserHandle user) {
+        return !disabledApps.contains(new PackageUserKey(packageName, user));
+    }
+
+    @Implementation
+    protected boolean isActivityEnabled(ComponentName component, UserHandle user) {
+        return !disabledActivities.contains(new ComponentKey(component, user));
+    }
+
+    @Implementation
+    protected LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
+        ResolveInfo ri = RuntimeEnvironment.application.getPackageManager()
+                .resolveActivity(intent, 0);
+        return getLauncherActivityInfo(ri.activityInfo);
+    }
+
+    public LauncherActivityInfo getLauncherActivityInfo(ActivityInfo activityInfo) {
+        return callConstructor(LauncherActivityInfo.class,
+                ClassParameter.from(Context.class, RuntimeEnvironment.application),
+                ClassParameter.from(ActivityInfo.class, activityInfo),
+                ClassParameter.from(UserHandle.class, Process.myUserHandle()));
+    }
+
+    @Implementation
+    public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user)
+            throws PackageManager.NameNotFoundException {
+        return RuntimeEnvironment.application.getPackageManager()
+                .getApplicationInfo(packageName, flags);
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java
new file mode 100644
index 0000000..edf8edb
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 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.shadows;
+
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.SparseBooleanArray;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowUserManager;
+
+/**
+ * Extension of {@link ShadowUserManager} with missing shadow methods
+ */
+@Implements(value = UserManager.class)
+public class LShadowUserManager extends ShadowUserManager {
+
+    private final SparseBooleanArray mQuietUsers = new SparseBooleanArray();
+    private final SparseBooleanArray mLockedUsers = new SparseBooleanArray();
+
+    @Implementation
+    protected boolean isQuietModeEnabled(UserHandle userHandle) {
+        return mQuietUsers.get(userHandle.hashCode());
+    }
+
+    public void setQuietModeEnabled(UserHandle userHandle, boolean enabled) {
+        mQuietUsers.put(userHandle.hashCode(), enabled);
+    }
+
+    @Implementation
+    protected boolean isUserUnlocked(UserHandle userHandle) {
+        return !mLockedUsers.get(userHandle.hashCode());
+    }
+
+    public void setUserLocked(UserHandle userHandle, boolean enabled) {
+        mLockedUsers.put(userHandle.hashCode(), enabled);
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java
new file mode 100644
index 0000000..d56de3c
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 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.shadows;
+
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
+
+import static org.robolectric.shadow.api.Shadow.invokeConstructor;
+import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
+import static org.robolectric.util.ReflectionHelpers.setField;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.LooperExecutor;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * Shadow for {@link LooperExecutor} to provide reset functionality for static executors.
+ */
+@Implements(value = LooperExecutor.class, isInAndroidSdk = false)
+public class ShadowLooperExecutor {
+
+    // Keep reference to all created Loopers so they can be torn down after test
+    private static Set<LooperExecutor> executors =
+            Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>()));
+
+    @RealObject private LooperExecutor realExecutor;
+
+    @Implementation
+    protected void __constructor__(Looper looper) {
+        invokeConstructor(LooperExecutor.class, realExecutor, from(Looper.class, looper));
+        executors.add(realExecutor);
+    }
+
+    /**
+     * Re-initializes any executor which may have been reset when a test finished
+     */
+    public static void reinitializeStaticExecutors() {
+        for (LooperExecutor executor : new ArrayList<>(executors)) {
+            setField(executor, "mHandler",
+                    new Handler(createAndStartNewLooper(executor.getThread().getName())));
+        }
+    }
+}
diff --git a/tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java b/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
similarity index 100%
rename from tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
rename to robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
diff --git a/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java b/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
index a9c1a7c..7e873e8 100644
--- a/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
+++ b/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
@@ -10,6 +10,8 @@
  */
 public class TestLauncherProvider extends LauncherProvider {
 
+    private boolean mAllowLoadDefaultFavorites;
+
     @Override
     public boolean onCreate() {
         return true;
@@ -18,18 +20,26 @@
     @Override
     protected synchronized void createDbIfNotExists() {
         if (mOpenHelper == null) {
-            mOpenHelper = new MyDatabaseHelper(getContext());
+            mOpenHelper = new MyDatabaseHelper(getContext(), mAllowLoadDefaultFavorites);
         }
     }
 
+    public void setAllowLoadDefaultFavorites(boolean allowLoadDefaultFavorites) {
+        mAllowLoadDefaultFavorites = allowLoadDefaultFavorites;
+    }
+
     public SQLiteDatabase getDb() {
         createDbIfNotExists();
         return mOpenHelper.getWritableDatabase();
     }
 
     private static class MyDatabaseHelper extends DatabaseHelper {
-        public MyDatabaseHelper(Context context) {
+
+        private final boolean mAllowLoadDefaultFavorites;
+
+        MyDatabaseHelper(Context context, boolean allowLoadDefaultFavorites) {
             super(context, null);
+            mAllowLoadDefaultFavorites = allowLoadDefaultFavorites;
             initIds();
         }
 
@@ -39,7 +49,11 @@
         }
 
         @Override
-        protected void onEmptyDbCreated() { }
+        protected void onEmptyDbCreated() {
+            if (mAllowLoadDefaultFavorites) {
+                super.onEmptyDbCreated();
+            }
+        }
 
         @Override
         protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 42927ea..67fe038 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -484,8 +484,6 @@
      */
     private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) {
         Context ctx = getContext();
-        InvariantDeviceProfile grid = LauncherAppState.getIDP(ctx);
-
         String authority = Settings.Secure.getString(ctx.getContentResolver(),
                 "launcher3.layout.provider");
         if (TextUtils.isEmpty(authority)) {
@@ -497,13 +495,7 @@
             Log.e(TAG, "No provider found for authority " + authority);
             return null;
         }
-        Uri uri = new Uri.Builder().scheme("content").authority(authority).path("launcher_layout")
-                .appendQueryParameter("version", "1")
-                .appendQueryParameter("gridWidth", Integer.toString(grid.numColumns))
-                .appendQueryParameter("gridHeight", Integer.toString(grid.numRows))
-                .appendQueryParameter("hotseatSize", Integer.toString(grid.numHotseatIcons))
-                .build();
-
+        Uri uri = getLayoutUri(authority, ctx);
         try (InputStream in = ctx.getContentResolver().openInputStream(uri)) {
             // Read the full xml so that we fail early in case of any IO error.
             String layout = new String(IOUtils.toByteArray(in));
@@ -520,6 +512,16 @@
         }
     }
 
+    public static Uri getLayoutUri(String authority, Context ctx) {
+        InvariantDeviceProfile grid = LauncherAppState.getIDP(ctx);
+        return new Uri.Builder().scheme("content").authority(authority).path("launcher_layout")
+                .appendQueryParameter("version", "1")
+                .appendQueryParameter("gridWidth", Integer.toString(grid.numColumns))
+                .appendQueryParameter("gridHeight", Integer.toString(grid.numRows))
+                .appendQueryParameter("hotseatSize", Integer.toString(grid.numHotseatIcons))
+                .build();
+    }
+
     private DefaultLayoutParser getDefaultLayoutParser(AppWidgetHost widgetHost) {
         InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext());
         int defaultLayout = idp.defaultLayoutId;
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index ae30380..80c7056 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -126,6 +126,13 @@
     public static final TogglableFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = new TogglableFlag(
             "ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache");
 
+    public static final TogglableFlag ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER = new TogglableFlag(
+            "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", false,
+            "Show launcher preview in grid picker");
+
+    public static final TogglableFlag ENABLE_OVERVIEW_ACTIONS = new TogglableFlag(
+            "ENABLE_OVERVIEW_ACTIONS", false, "Show app actions in Overview");
+
     public static void initialize(Context context) {
         // Avoid the disk read for user builds
         if (Utilities.IS_DEBUG_DEVICE) {
diff --git a/src/com/android/launcher3/config/FlagTogglerPrefUi.java b/src/com/android/launcher3/config/FlagTogglerPrefUi.java
index 200938d..a7e3732 100644
--- a/src/com/android/launcher3/config/FlagTogglerPrefUi.java
+++ b/src/com/android/launcher3/config/FlagTogglerPrefUi.java
@@ -25,15 +25,15 @@
 import android.view.MenuItem;
 import android.widget.Toast;
 
+import androidx.preference.PreferenceDataStore;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.SwitchPreference;
+
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags.BaseTogglableFlag;
 import com.android.launcher3.uioverrides.TogglableFlag;
 
-import androidx.preference.PreferenceDataStore;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceGroup;
-import androidx.preference.SwitchPreference;
-
 /**
  * Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}.
  */
@@ -41,7 +41,7 @@
 
     private static final String TAG = "FlagTogglerPrefFrag";
 
-    private final PreferenceFragment mFragment;
+    private final PreferenceFragmentCompat mFragment;
     private final Context mContext;
     private final SharedPreferences mSharedPreferences;
 
@@ -72,7 +72,7 @@
         }
     };
 
-    public FlagTogglerPrefUi(PreferenceFragment fragment) {
+    public FlagTogglerPrefUi(PreferenceFragmentCompat fragment) {
         mFragment = fragment;
         mContext = fragment.getActivity();
         mSharedPreferences = mContext.getSharedPreferences(
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 33da582..f59a192 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -417,7 +417,8 @@
      * Show suggested folder title.
      */
     public void showSuggestedTitle(String[] suggestName) {
-        if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && mInfo.contents.size() == 2) {
+        if (FeatureFlags.FOLDER_NAME_SUGGEST.get()
+                && TextUtils.isEmpty(mFolderName.getText().toString())) {
             if (suggestName.length > 0 && !TextUtils.isEmpty(suggestName[0])) {
                 mFolderName.setHint(suggestName[0]);
                 mFolderName.setText(suggestName[0]);
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 2badb6e..0c5535f 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -19,6 +19,10 @@
 import static android.view.View.MeasureSpec.makeMeasureSpec;
 import static android.view.View.VISIBLE;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER;
+import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
+
 import android.annotation.TargetApi;
 import android.app.Fragment;
 import android.content.Context;
@@ -48,6 +52,10 @@
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -58,11 +66,20 @@
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.model.AllAppsList;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.LoaderResults;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
 
+import java.util.ArrayList;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
@@ -241,22 +258,59 @@
         }
 
         private void renderScreenShot(Canvas canvas) {
-            // Add hotseat icons
-            for (int i = 0; i < mIdp.numHotseatIcons; i++) {
-                WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
-                info.container = Favorites.CONTAINER_HOTSEAT;
-                info.screenId = i;
-                inflateAndAddIcon(info);
-            }
+            if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
+                final LauncherModel launcherModel = LauncherAppState.getInstance(
+                        mContext).getModel();
+                final WorkspaceItemsInfoFetcher fetcher = new WorkspaceItemsInfoFetcher();
+                launcherModel.enqueueModelUpdateTask(fetcher);
+                ArrayList<ItemInfo> workspaceItems;
+                try {
+                    workspaceItems = fetcher.mTask.get(5, TimeUnit.SECONDS);
+                } catch (InterruptedException | ExecutionException | TimeoutException e) {
+                    Log.d(TAG, "Error fetching workspace items info", e);
+                    return;
+                }
 
-            // Add workspace icons
-            for (int i = 0; i < mIdp.numColumns; i++) {
-                WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
-                info.container = Favorites.CONTAINER_DESKTOP;
-                info.screenId = 0;
-                info.cellX = i;
-                info.cellY = mIdp.numRows - 1;
-                inflateAndAddIcon(info);
+                // Separate the items that are on the current screen, and all the other remaining
+                // items
+                ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
+                ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
+
+                filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceItems,
+                        currentWorkspaceItems, otherWorkspaceItems);
+                sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
+
+                for (ItemInfo itemInfo : currentWorkspaceItems) {
+                    switch (itemInfo.itemType) {
+                        case Favorites.ITEM_TYPE_APPLICATION:
+                        case Favorites.ITEM_TYPE_SHORTCUT:
+                        case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                            inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
+                            break;
+                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                            // TODO: for folder implementation here.
+                            break;
+                        default:
+                            break;
+                    }
+                }
+            } else {
+                // Add hotseat icons
+                for (int i = 0; i < mIdp.numHotseatIcons; i++) {
+                    WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
+                    info.container = Favorites.CONTAINER_HOTSEAT;
+                    info.screenId = i;
+                    inflateAndAddIcon(info);
+                }
+                // Add workspace icons
+                for (int i = 0; i < mIdp.numColumns; i++) {
+                    WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
+                    info.container = Favorites.CONTAINER_DESKTOP;
+                    info.screenId = 0;
+                    info.cellX = i;
+                    info.cellY = mIdp.numRows - 1;
+                    inflateAndAddIcon(info);
+                }
             }
 
             // Add first page QSB
@@ -286,6 +340,42 @@
         }
     }
 
+    private static class WorkspaceItemsInfoFetcher implements Callable<ArrayList<ItemInfo>>,
+            LauncherModel.ModelUpdateTask {
+
+        private final FutureTask<ArrayList<ItemInfo>> mTask = new FutureTask<>(this);
+
+        private LauncherAppState mApp;
+        private LauncherModel mModel;
+        private BgDataModel mBgDataModel;
+        private AllAppsList mAllAppsList;
+
+        @Override
+        public void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel,
+                AllAppsList allAppsList, Executor uiExecutor) {
+            mApp = app;
+            mModel = model;
+            mBgDataModel = dataModel;
+            mAllAppsList = allAppsList;
+        }
+
+        @Override
+        public void run() {
+            mTask.run();
+        }
+
+        @Override
+        public ArrayList<ItemInfo> call() throws Exception {
+            if (!mModel.isModelLoaded()) {
+                Log.d(TAG, "Workspace not loaded, loading now");
+                mModel.startLoaderForResults(
+                        new LoaderResults(mApp, mBgDataModel, mAllAppsList, 0, null));
+                return new ArrayList<>();
+            }
+            return mBgDataModel.workspaceItems;
+        }
+    }
+
     private static void measureView(View view, int width, int height) {
         view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
         view.layout(0, 0, width, height);
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index a00a6bd..76c2951 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.os.Looper;
@@ -27,20 +29,15 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.PagedView;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LooperIdleLock;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
 import java.util.concurrent.Executor;
 
 /**
@@ -123,8 +120,9 @@
                 otherWorkspaceItems);
         filterCurrentWorkspaceItems(currentScreenId, appWidgets, currentAppWidgets,
                 otherAppWidgets);
-        sortWorkspaceItemsSpatially(currentWorkspaceItems);
-        sortWorkspaceItemsSpatially(otherWorkspaceItems);
+        final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
+        sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
+        sortWorkspaceItemsSpatially(idp, otherWorkspaceItems);
 
         // Tell the workspace that we're about to start binding items
         executeCallbacksTask(c -> {
@@ -169,89 +167,6 @@
         }
     }
 
-
-    /** Filters the set of items who are directly or indirectly (via another container) on the
-     * specified screen. */
-    public static <T extends ItemInfo> void filterCurrentWorkspaceItems(int currentScreenId,
-            ArrayList<T> allWorkspaceItems,
-            ArrayList<T> currentScreenItems,
-            ArrayList<T> otherScreenItems) {
-        // Purge any null ItemInfos
-        Iterator<T> iter = allWorkspaceItems.iterator();
-        while (iter.hasNext()) {
-            ItemInfo i = iter.next();
-            if (i == null) {
-                iter.remove();
-            }
-        }
-
-        // Order the set of items by their containers first, this allows use to walk through the
-        // list sequentially, build up a list of containers that are in the specified screen,
-        // as well as all items in those containers.
-        IntSet itemsOnScreen = new IntSet();
-        Collections.sort(allWorkspaceItems,
-                (lhs, rhs) -> Integer.compare(lhs.container, rhs.container));
-
-        for (T info : allWorkspaceItems) {
-            if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                if (info.screenId == currentScreenId) {
-                    currentScreenItems.add(info);
-                    itemsOnScreen.add(info.id);
-                } else {
-                    otherScreenItems.add(info);
-                }
-            } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                currentScreenItems.add(info);
-                itemsOnScreen.add(info.id);
-            } else {
-                if (itemsOnScreen.contains(info.container)) {
-                    currentScreenItems.add(info);
-                    itemsOnScreen.add(info.id);
-                } else {
-                    otherScreenItems.add(info);
-                }
-            }
-        }
-    }
-
-    /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
-     * right) */
-    protected void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
-        final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile();
-        final int screenCols = profile.numColumns;
-        final int screenCellCount = profile.numColumns * profile.numRows;
-        Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
-            @Override
-            public int compare(ItemInfo lhs, ItemInfo rhs) {
-                if (lhs.container == rhs.container) {
-                    // Within containers, order by their spatial position in that container
-                    switch (lhs.container) {
-                        case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
-                            int lr = (lhs.screenId * screenCellCount +
-                                    lhs.cellY * screenCols + lhs.cellX);
-                            int rr = (rhs.screenId * screenCellCount +
-                                    rhs.cellY * screenCols + rhs.cellX);
-                            return Integer.compare(lr, rr);
-                        }
-                        case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
-                            // We currently use the screen id as the rank
-                            return Integer.compare(lhs.screenId, rhs.screenId);
-                        }
-                        default:
-                            if (FeatureFlags.IS_DOGFOOD_BUILD) {
-                                throw new RuntimeException("Unexpected container type when " +
-                                        "sorting workspace items.");
-                            }
-                            return 0;
-                    }
-                } else {
-                    // Between containers, order by hotseat, desktop
-                    return Integer.compare(lhs.container, rhs.container);
-                }
-            }
-        });
-    }
-
     protected void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems,
             final Executor executor) {
         // Bind the workspace items
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 3a4085c..cc994df 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
-import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.PackageManagerHelper.isSystemApp;
 
@@ -42,6 +42,8 @@
 import android.util.MutableInt;
 import android.util.TimingLogger;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InstallShortcutReceiver;
@@ -271,7 +273,8 @@
         this.notify();
     }
 
-    private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
+    @VisibleForTesting
+    void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
         final Context context = mApp.getContext();
         final ContentResolver contentResolver = context.getContentResolver();
         final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
new file mode 100644
index 0000000..628dd95
--- /dev/null
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2019 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.model;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.IntSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * Utils class for {@link com.android.launcher3.LauncherModel}.
+ */
+public class ModelUtils {
+
+    /**
+     * Filters the set of items who are directly or indirectly (via another container) on the
+     * specified screen.
+     */
+    public static <T extends ItemInfo> void filterCurrentWorkspaceItems(int currentScreenId,
+            ArrayList<T> allWorkspaceItems,
+            ArrayList<T> currentScreenItems,
+            ArrayList<T> otherScreenItems) {
+        // Purge any null ItemInfos
+        Iterator<T> iter = allWorkspaceItems.iterator();
+        while (iter.hasNext()) {
+            ItemInfo i = iter.next();
+            if (i == null) {
+                iter.remove();
+            }
+        }
+        // Order the set of items by their containers first, this allows use to walk through the
+        // list sequentially, build up a list of containers that are in the specified screen,
+        // as well as all items in those containers.
+        IntSet itemsOnScreen = new IntSet();
+        Collections.sort(allWorkspaceItems,
+                (lhs, rhs) -> Integer.compare(lhs.container, rhs.container));
+        for (T info : allWorkspaceItems) {
+            if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                if (info.screenId == currentScreenId) {
+                    currentScreenItems.add(info);
+                    itemsOnScreen.add(info.id);
+                } else {
+                    otherScreenItems.add(info);
+                }
+            } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                currentScreenItems.add(info);
+                itemsOnScreen.add(info.id);
+            } else {
+                if (itemsOnScreen.contains(info.container)) {
+                    currentScreenItems.add(info);
+                    itemsOnScreen.add(info.id);
+                } else {
+                    otherScreenItems.add(info);
+                }
+            }
+        }
+    }
+
+    /**
+     * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right)
+     */
+    public static void sortWorkspaceItemsSpatially(InvariantDeviceProfile profile,
+            ArrayList<ItemInfo> workspaceItems) {
+        final int screenCols = profile.numColumns;
+        final int screenCellCount = profile.numColumns * profile.numRows;
+        Collections.sort(workspaceItems, (lhs, rhs) -> {
+            if (lhs.container == rhs.container) {
+                // Within containers, order by their spatial position in that container
+                switch (lhs.container) {
+                    case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
+                        int lr = (lhs.screenId * screenCellCount + lhs.cellY * screenCols
+                                + lhs.cellX);
+                        int rr = (rhs.screenId * screenCellCount + +rhs.cellY * screenCols
+                                + rhs.cellX);
+                        return Integer.compare(lr, rr);
+                    }
+                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
+                        // We currently use the screen id as the rank
+                        return Integer.compare(lhs.screenId, rhs.screenId);
+                    }
+                    default:
+                        if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                            throw new RuntimeException(
+                                    "Unexpected container type when sorting workspace items.");
+                        }
+                        return 0;
+                }
+            } else {
+                // Between containers, order by hotseat, desktop
+                return Integer.compare(lhs.container, rhs.container);
+            }
+        });
+    }
+}
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index c69ace9..bdf3a69 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -103,7 +103,7 @@
      */
     public void addOrMoveItemInDatabase(ItemInfo item,
             int container, int screenId, int cellX, int cellY) {
-        if (item.container == ItemInfo.NO_ID) {
+        if (item.id == ItemInfo.NO_ID) {
             // From all apps
             addItemToDatabase(item, container, screenId, cellX, cellY);
         } else {
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index 3668313..049fda9 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -44,7 +44,7 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceCategory;
 import androidx.preference.PreferenceDataStore;
-import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceScreen;
 import androidx.preference.PreferenceViewHolder;
 import androidx.preference.SwitchPreference;
@@ -64,7 +64,7 @@
  * See {@link FeatureFlags}.
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class DeveloperOptionsFragment extends PreferenceFragment {
+public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
 
     private static final String ACTION_PLUGIN_SETTINGS = "com.android.systemui.action.PLUGIN_SETTINGS";
     private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 18b6094..12085c8 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -16,14 +16,13 @@
 
 package com.android.launcher3.settings;
 
+import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
+
 import static com.android.launcher3.SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY;
 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
 import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
 import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
 
-import android.app.Activity;
-import android.app.DialogFragment;
-import android.app.Fragment;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -32,6 +31,19 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback;
+import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartScreenCallback;
+import androidx.preference.PreferenceGroup.PreferencePositionCallback;
+import androidx.preference.PreferenceScreen;
+import androidx.recyclerview.widget.RecyclerView;
+
 import com.android.launcher3.LauncherFiles;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -40,18 +52,10 @@
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.SecureSettingsObserver;
 
-import androidx.preference.Preference;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceFragment.OnPreferenceStartFragmentCallback;
-import androidx.preference.PreferenceFragment.OnPreferenceStartScreenCallback;
-import androidx.preference.PreferenceGroup.PreferencePositionCallback;
-import androidx.preference.PreferenceScreen;
-import androidx.recyclerview.widget.RecyclerView;
-
 /**
  * Settings activity for Launcher. Currently implements the following setting: Allow rotation
  */
-public class SettingsActivity extends Activity
+public class SettingsActivity extends FragmentActivity
         implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback,
         SharedPreferences.OnSharedPreferenceChangeListener{
 
@@ -80,15 +84,16 @@
                 args.putString(EXTRA_FRAGMENT_ARG_KEY, prefKey);
             }
 
-            Fragment f = Fragment.instantiate(
-                    this, getString(R.string.settings_fragment_name), args);
+            final FragmentManager fm = getSupportFragmentManager();
+            final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(),
+                    getString(R.string.settings_fragment_name));
+            f.setArguments(args);
             // Display the fragment as the main content.
-            getFragmentManager().beginTransaction()
-                    .replace(android.R.id.content, f)
-                    .commit();
+            fm.beginTransaction().replace(android.R.id.content, f).commit();
         }
         Utilities.getPrefs(getApplicationContext()).registerOnSharedPreferenceChangeListener(this);
     }
+
     @Override
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
         if (GRID_OPTIONS_PREFERENCE_KEY.equals(key)) {
@@ -112,41 +117,39 @@
     }
 
     private boolean startFragment(String fragment, Bundle args, String key) {
-        if (Utilities.ATLEAST_P && getFragmentManager().isStateSaved()) {
+        if (Utilities.ATLEAST_P && getSupportFragmentManager().isStateSaved()) {
             // Sometimes onClick can come after onPause because of being posted on the handler.
             // Skip starting new fragments in that case.
             return false;
         }
-        Fragment f = Fragment.instantiate(this, fragment, args);
+        final FragmentManager fm = getSupportFragmentManager();
+        final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(), fragment);
+        f.setArguments(args);
         if (f instanceof DialogFragment) {
-            ((DialogFragment) f).show(getFragmentManager(), key);
+            ((DialogFragment) f).show(getSupportFragmentManager(), key);
         } else {
-            getFragmentManager()
-                    .beginTransaction()
-                    .replace(android.R.id.content, f)
-                    .addToBackStack(key)
-                    .commit();
+            fm.beginTransaction().replace(android.R.id.content, f).addToBackStack(key).commit();
         }
         return true;
     }
 
     @Override
     public boolean onPreferenceStartFragment(
-            PreferenceFragment preferenceFragment, Preference pref) {
+            PreferenceFragmentCompat preferenceFragment, Preference pref) {
         return startFragment(pref.getFragment(), pref.getExtras(), pref.getKey());
     }
 
     @Override
-    public boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref) {
+    public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref) {
         Bundle args = new Bundle();
-        args.putString(PreferenceFragment.ARG_PREFERENCE_ROOT, pref.getKey());
+        args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, pref.getKey());
         return startFragment(getString(R.string.settings_fragment_name), args, pref.getKey());
     }
 
     /**
      * This fragment shows the launcher preferences.
      */
-    public static class LauncherSettingsFragment extends PreferenceFragment {
+    public static class LauncherSettingsFragment extends PreferenceFragmentCompat {
 
         private SecureSettingsObserver mNotificationDotsObserver;
 
@@ -248,6 +251,8 @@
                 if (highlighter != null) {
                     getView().postDelayed(highlighter, DELAY_HIGHLIGHT_DURATION_MILLIS);
                     mPreferenceHighlighted = true;
+                } else {
+                    requestAccessibilityFocus(getListView());
                 }
             }
         }
@@ -268,6 +273,15 @@
             return position >= 0 ? new PreferenceHighlighter(list, position) : null;
         }
 
+        private void requestAccessibilityFocus(@NonNull final RecyclerView rv) {
+            rv.post(() -> {
+                if (!rv.hasFocus() && rv.getChildCount() > 0) {
+                    rv.getChildAt(0)
+                            .performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null);
+                }
+            });
+        }
+
         @Override
         public void onDestroy() {
             if (mNotificationDotsObserver != null) {
diff --git a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
deleted file mode 100644
index 1d89d6e..0000000
--- a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * Copyright (C) 2019 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.ui;
-
-import static org.junit.Assert.assertTrue;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.testcomponent.TestCommandProvider;
-import com.android.launcher3.util.LauncherLayoutBuilder;
-import com.android.launcher3.util.rule.ShellCommandRule;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.OutputStreamWriter;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class DefaultLayoutProviderTest extends AbstractLauncherUiTest {
-
-    @Rule
-    public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
-
-    private static final String SETTINGS_APP = "com.android.settings";
-
-    private Context mContext;
-    private String mAuthority;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        mContext = InstrumentationRegistry.getContext();
-
-        PackageManager pm = mTargetContext.getPackageManager();
-        ProviderInfo pi = pm.getProviderInfo(new ComponentName(mContext,
-                TestCommandProvider.class), 0);
-        mAuthority = pi.authority;
-    }
-
-    @Test
-    public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception {
-        writeLayout(new LauncherLayoutBuilder().atHotseat(0).putApp(SETTINGS_APP, SETTINGS_APP));
-
-        // Launch the home activity
-        mDevice.pressHome();
-
-        mLauncher.getWorkspace().getHotseatAppIcon(getSettingsApp().getLabel().toString());
-    }
-
-    @Test
-    public void testCustomProfileLoaded_with_widget() throws Exception {
-        // A non-restored widget with no config screen gets restored automatically.
-        LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
-
-        writeLayout(new LauncherLayoutBuilder().atWorkspace(0, 1, 0)
-                .putWidget(info.getComponent().getPackageName(),
-                        info.getComponent().getClassName(), 2, 2));
-
-        // Launch the home activity
-        mDevice.pressHome();
-
-        // Verify widget present
-        assertTrue("Widget is not present",
-                mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
-    }
-
-    @Test
-    public void testCustomProfileLoaded_with_folder() throws Exception {
-        writeLayout(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy)
-                .addApp(SETTINGS_APP, SETTINGS_APP)
-                .addApp(SETTINGS_APP, SETTINGS_APP)
-                .addApp(SETTINGS_APP, SETTINGS_APP)
-                .build());
-
-        // Launch the home activity
-        mDevice.pressHome();
-
-        mLauncher.getWorkspace().getHotseatFolder("Folder: Copy");
-    }
-
-    @After
-    public void cleanup() throws Exception {
-        mDevice.executeShellCommand("settings delete secure launcher3.layout.provider");
-    }
-
-    private void writeLayout(LauncherLayoutBuilder builder) throws Exception {
-        mDevice.executeShellCommand("settings put secure launcher3.layout.provider " + mAuthority);
-        ParcelFileDescriptor pfd = mTargetContext.getContentResolver().openFileDescriptor(
-                Uri.parse("content://" + mAuthority + "/launcher_layout"), "w");
-
-        try (OutputStreamWriter writer = new OutputStreamWriter(new AutoCloseOutputStream(pfd))) {
-            builder.build(writer);
-        }
-        clearLauncherData();
-    }
-}
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 6583d32..d9ae778 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.tapl;
 
 import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 
 import android.graphics.Point;
 import android.os.SystemClock;
@@ -54,13 +55,13 @@
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to switch from background to overview")) {
             verifyActiveContainer();
-            goToOverviewUnchecked(BACKGROUND_APP_STATE_ORDINAL);
+            goToOverviewUnchecked();
             return mLauncher.isFallbackOverview() ?
                     new BaseOverview(mLauncher) : new Overview(mLauncher);
         }
     }
 
-    protected void goToOverviewUnchecked(int expectedState) {
+    protected void goToOverviewUnchecked() {
         switch (mLauncher.getNavigationModel()) {
             case ZERO_BUTTON: {
                 final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
@@ -81,9 +82,11 @@
                                 start,
                                 end),
                         event -> TestProtocol.PAUSE_DETECTED_MESSAGE.equals(event.getClassName()),
-                        "Pause wasn't detected");
-                mLauncher.sendPointer(
-                        downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, end);
+                        () -> "Pause wasn't detected");
+                mLauncher.runToState(
+                        () -> mLauncher.sendPointer(
+                                downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, end),
+                        OVERVIEW_STATE_ORDINAL);
                 break;
             }
 
@@ -105,17 +108,14 @@
                     startY = endY = mLauncher.getDevice().getDisplayHeight() / 2;
                 }
 
-                if (mLauncher.isFallbackOverview()) {
-                    mLauncher.linearGesture(startX, startY, endX, endY, 10, false);
-                    new BaseOverview(mLauncher);
-                } else {
-                    mLauncher.swipeToState(startX, startY, endX, endY, 10, expectedState);
-                }
+                mLauncher.swipeToState(startX, startY, endX, endY, 10, OVERVIEW_STATE_ORDINAL);
                 break;
             }
 
             case THREE_BUTTON:
-                mLauncher.waitForSystemUiObject("recent_apps").click();
+                mLauncher.runToState(
+                        () -> mLauncher.waitForSystemUiObject("recent_apps").click(),
+                        OVERVIEW_STATE_ORDINAL);
                 break;
         }
     }
@@ -167,7 +167,7 @@
             case THREE_BUTTON:
                 // Double press the recents button.
                 UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
-                recentsButton.click();
+                mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL);
                 mLauncher.getOverview();
                 recentsButton.click();
                 break;
diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java
index e0fe933..1e4d937 100644
--- a/tests/tapl/com/android/launcher3/tapl/Home.java
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.tapl;
 
-import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
 
 import androidx.annotation.NonNull;
@@ -52,7 +51,7 @@
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to switch from home to overview")) {
             verifyActiveContainer();
-            goToOverviewUnchecked(OVERVIEW_STATE_ORDINAL);
+            goToOverviewUnchecked();
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                     "performed the switch action")) {
                 return new Overview(mLauncher);
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index df80a51..6881197 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -56,7 +56,7 @@
         mLauncher.executeAndWaitForEvent(
                 () -> mObject.click(),
                 event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
-                "Launching an app didn't open a new window: " + mObject.getText());
+                () -> "Launching an app didn't open a new window: " + mObject.getText());
 
         mLauncher.assertTrue(
                 "App didn't start: " + selector,
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index ccf98ae..2fea1b7 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -22,7 +22,6 @@
 import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
 
 import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName;
-import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
 
 import android.app.ActivityManager;
@@ -78,6 +77,8 @@
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 /**
  * The main tapl object. The only object that can be explicitly constructed by the using code. It
@@ -510,7 +511,7 @@
     }
 
     Parcelable executeAndWaitForEvent(Runnable command,
-            UiAutomation.AccessibilityEventFilter eventFilter, String message) {
+            UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message) {
         try {
             final AccessibilityEvent event =
                     mInstrumentation.getUiAutomation().executeAndWaitForEvent(
@@ -518,7 +519,7 @@
             assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event);
             return event.getParcelableData();
         } catch (TimeoutException e) {
-            fail(message);
+            fail(message.get());
             return null;
         }
     }
@@ -557,14 +558,12 @@
                 log("Hierarchy before swiping up to home");
                 dumpViewHierarchy();
                 log(action = "swiping up to home from " + getVisibleStateMessage());
-                final int finalState = mDevice.hasObject(By.pkg(getLauncherPackageName()))
-                        ? NORMAL_STATE_ORDINAL : BACKGROUND_APP_STATE_ORDINAL;
 
                 try (LauncherInstrumentation.Closable c = addContextLayer(action)) {
                     swipeToState(
                             displaySize.x / 2, displaySize.y - 1,
                             displaySize.x / 2, 0,
-                            ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, finalState);
+                            ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL);
                 }
             }
         } else {
@@ -575,7 +574,7 @@
                         waitForSystemUiObject("home").click();
                     },
                     event -> true,
-                    "Pressing Home didn't produce any events");
+                    () -> "Pressing Home didn't produce any events");
             mDevice.waitForIdle();
         }
         try (LauncherInstrumentation.Closable c = addContextLayer(
@@ -770,14 +769,38 @@
         return mDevice;
     }
 
+    private static String eventListToString(List<Integer> actualEvents) {
+        if (actualEvents.isEmpty()) return "no events";
+
+        return "["
+                + actualEvents.stream()
+                .map(state -> TestProtocol.stateOrdinalToString(state))
+                .collect(Collectors.joining(", "))
+                + "]";
+    }
+
+    void runToState(Runnable command, int expectedState) {
+        final List<Integer> actualEvents = new ArrayList<>();
+        executeAndWaitForEvent(
+                command,
+                event -> isSwitchToStateEvent(event, expectedState, actualEvents),
+                () -> "Failed to receive an event for the swipe end: expected "
+                        + TestProtocol.stateOrdinalToString(expectedState)
+                        + ", actual: " + eventListToString(actualEvents));
+    }
+
+    private boolean isSwitchToStateEvent(
+            AccessibilityEvent event, int expectedState, List<Integer> actualEvents) {
+        if (!TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName())) return false;
+
+        final Bundle parcel = (Bundle) event.getParcelableData();
+        final int actualState = parcel.getInt(TestProtocol.STATE_FIELD);
+        actualEvents.add(actualState);
+        return actualState == expectedState;
+    }
+
     void swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState) {
-        final Bundle parcel = (Bundle) executeAndWaitForEvent(
-                () -> linearGesture(startX, startY, endX, endY, steps, false),
-                event -> TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName()),
-                "Swipe failed to receive an event for the swipe end");
-        assertEquals("Swipe switched launcher to a wrong state;",
-                TestProtocol.stateOrdinalToString(expectedState),
-                TestProtocol.stateOrdinalToString(parcel.getInt(TestProtocol.STATE_FIELD)));
+        runToState(() -> linearGesture(startX, startY, endX, endY, steps, false), expectedState);
     }
 
     int getBottomGestureSize() {
@@ -863,7 +886,7 @@
         executeAndWaitForEvent(
                 () -> linearGesture(startX, startY, endX, endY, steps, slowDown),
                 event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
-                "Didn't receive a scroll end message: " + startX + ", " + startY
+                () -> "Didn't receive a scroll end message: " + startX + ", " + startY
                         + ", " + endX + ", " + endY);
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 2ee424b..46f8ba5 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -67,8 +67,8 @@
             mLauncher.executeAndWaitForEvent(
                     () -> mTask.click(),
                     event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
-                    "Launching task didn't open a new window: " +
-                            mTask.getParent().getContentDescription());
+                    () -> "Launching task didn't open a new window: "
+                            + mTask.getParent().getContentDescription());
         }
         return new Background(mLauncher);
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 81d343d..8a53ef1 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -17,6 +17,8 @@
 package com.android.launcher3.tapl;
 
 import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
 
 import static junit.framework.TestCase.assertTrue;
 
@@ -165,14 +167,21 @@
         LauncherInstrumentation.log("dragIconToWorkspace: begin");
         final Point launchableCenter = launchable.getObject().getVisibleCenter();
         final long downTime = SystemClock.uptimeMillis();
-        launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, launchableCenter);
-        LauncherInstrumentation.log("dragIconToWorkspace: sent down");
-        launcher.waitForLauncherObject(longPressIndicator);
-        LauncherInstrumentation.log("dragIconToWorkspace: indicator");
-        launcher.movePointer(launchableCenter, dest, 10, downTime, true);
+        launcher.runToState(
+                () -> {
+                    launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
+                            launchableCenter);
+                    LauncherInstrumentation.log("dragIconToWorkspace: sent down");
+                    launcher.waitForLauncherObject(longPressIndicator);
+                    LauncherInstrumentation.log("dragIconToWorkspace: indicator");
+                    launcher.movePointer(launchableCenter, dest, 10, downTime, true);
+                },
+                SPRING_LOADED_STATE_ORDINAL);
         LauncherInstrumentation.log("dragIconToWorkspace: moved pointer");
-        launcher.sendPointer(
-                downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest);
+        launcher.runToState(
+                () -> launcher.sendPointer(
+                        downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest),
+                NORMAL_STATE_ORDINAL);
         LauncherInstrumentation.log("dragIconToWorkspace: end");
         launcher.waitUntilGone("drop_target_bar");
     }