Merge "Fix icon text size being calculated in DP instead of SP." into sc-dev
diff --git a/Android.bp b/Android.bp
index cca48ce..002f6fe 100644
--- a/Android.bp
+++ b/Android.bp
@@ -93,3 +93,93 @@
     sdk_version: "current",
     min_sdk_version: "28",
 }
+
+//
+// Build rule for Launcher3 dependencies lib.
+//
+android_library {
+    name: "Launcher3CommonDepsLib",
+    static_libs: [
+        "androidx.recyclerview_recyclerview",
+        "androidx.dynamicanimation_dynamicanimation",
+        "androidx.preference_preference",
+        "androidx.slice_slice-view",
+        "iconloader_base",
+        "LauncherPluginLib",
+        "launcher_quickstep_log_protos_lite"
+    ],
+    srcs: [
+        "src_build_config/**/*.java",
+    ],
+    resource_dirs: ["res"],
+    optimize: {
+        enabled: false,
+    },
+    sdk_version: "current",
+    min_sdk_version: "26",
+    manifest: "AndroidManifest-common.xml",
+}
+
+//
+// Build rule for Launcher3 app.
+//
+android_app {
+    name: "Launcher3",
+
+    static_libs: [
+        "Launcher3CommonDepsLib",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "src_shortcuts_overrides/**/*.java",
+        "src_ui_overrides/**/*.java",
+        "ext_tests/src/**/*.java",
+    ],
+    resource_dirs: [
+        "ext_tests/res",
+    ],
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+        // Proguard is disable for testing. Derivarive prjects to keep proguard enabled
+        enabled: false,
+    },
+
+    sdk_version: "current",
+    min_sdk_version: "26",
+    target_sdk_version: "29",
+    privileged: true,
+    system_ext_specific: true,
+
+    overrides: [
+        "Home",
+        "Launcher2",
+    ],
+    required: ["privapp_whitelist_com.android.launcher3"],
+
+    jacoco: {
+        include_filter: ["com.android.launcher3.**"],
+    },
+    additional_manifests: [
+        "AndroidManifest-common.xml",
+    ],
+}
+
+//
+// Launcher Robolectric test target.
+//
+java_library {
+    name: "Launcher3TestCommon",
+    libs: [
+        "Launcher3CommonDepsLib",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "src_shortcuts_overrides/**/*.java",
+        "src_ui_overrides/**/*.java",
+        "ext_tests/src/**/*.java",
+        "tests/src_common/**/*.java",
+    ],
+    target_sdk_version: "29",
+    sdk_version: "current",
+    min_sdk_version: "26",
+}
diff --git a/Android.mk b/Android.mk
index 304935b..89870a6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -17,79 +17,6 @@
 LOCAL_PATH := $(call my-dir)
 
 #
-# Build rule for Launcher3 dependencies lib.
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT2_ONLY := true
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    androidx.recyclerview_recyclerview \
-    androidx.dynamicanimation_dynamicanimation \
-    androidx.preference_preference \
-    androidx.slice_slice-view \
-    iconloader_base
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    LauncherPluginLib \
-    launcher_quickstep_log_protos_lite \
-    search_ui
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src_build_config) \
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 26
-LOCAL_MODULE := Launcher3CommonDepsLib
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_MANIFEST_FILE := AndroidManifest-common.xml
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-#
-# Build rule for Launcher3 app.
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src) \
-    $(call all-java-files-under, src_shortcuts_overrides) \
-    $(call all-java-files-under, src_ui_overrides) \
-    $(call all-java-files-under, ext_tests/src)
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/ext_tests/res
-
-LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-# Proguard is disable for testing. Derivarive prjects to keep proguard enabled
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 26
-LOCAL_PACKAGE_NAME := Launcher3
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_SYSTEM_EXT_MODULE := true
-LOCAL_OVERRIDES_PACKAGES := Home Launcher2
-LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest-common.xml
-
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
-
-include $(BUILD_PACKAGE)
-
-#
 # Build rule for Launcher3 Go app for Android Go devices.
 #
 include $(CLEAR_VARS)
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index a202095..cc5e1cb 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -30,6 +30,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -53,10 +54,15 @@
      *
      * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
      */
-    public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsList(Context context) {
+    public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsListForPicker(Context context) {
         return EMPTY_WIDGET_LIST;
     }
 
+    /** Returns a mapping of packages to their widgets without static shortcuts. */
+    public synchronized Map<PackageUserKey, List<WidgetItem>> getAllWidgetsWithoutShortcuts() {
+        return Map.of();
+    }
+
     /**
      * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
      *                    only widgets and shortcuts associated with the package/user are.
diff --git a/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
new file mode 100644
index 0000000..70a143e
--- /dev/null
+++ b/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2021 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 com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetId;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Process;
+import android.os.UserHandle;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowAppWidgetManager;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RunWith(RobolectricTestRunner.class)
+public final class WidgetsPredicationUpdateTaskTest {
+
+    private AppWidgetProviderInfo mApp1Provider1 = new AppWidgetProviderInfo();
+    private AppWidgetProviderInfo mApp1Provider2 = new AppWidgetProviderInfo();
+    private AppWidgetProviderInfo mApp2Provider1 = new AppWidgetProviderInfo();
+    private AppWidgetProviderInfo mApp4Provider1 = new AppWidgetProviderInfo();
+    private AppWidgetProviderInfo mApp4Provider2 = new AppWidgetProviderInfo();
+    private AppWidgetProviderInfo mApp5Provider1 = new AppWidgetProviderInfo();
+
+    private FakeBgDataModelCallback mCallback = new FakeBgDataModelCallback();
+    private Context mContext;
+    private LauncherModelHelper mModelHelper;
+    private UserHandle mUserHandle;
+    private InvariantDeviceProfile mTestProfile;
+
+    @Mock
+    private IconCache mIconCache;
+
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        doAnswer(invocation -> {
+            ComponentWithLabel componentWithLabel = invocation.getArgument(0);
+            return componentWithLabel.getComponent().getShortClassName();
+        }).when(mIconCache).getTitleNoCache(any());
+
+        mContext = RuntimeEnvironment.application;
+        mModelHelper = new LauncherModelHelper();
+        mUserHandle = Process.myUserHandle();
+        mTestProfile = new InvariantDeviceProfile();
+        // 2 widgets, app4/provider1 & app5/provider1, have already been added to the workspace.
+        mModelHelper.initializeData("/widgets_predication_update_task_data.txt");
+
+        ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
+        mApp1Provider1.provider = ComponentName.createRelative("app1", "provider1");
+        ReflectionHelpers.setField(mApp1Provider1, "providerInfo",
+                packageManager.addReceiverIfNotPresent(mApp1Provider1.provider));
+        mApp1Provider2.provider = ComponentName.createRelative("app1", "provider2");
+        ReflectionHelpers.setField(mApp1Provider2, "providerInfo",
+                packageManager.addReceiverIfNotPresent(mApp1Provider2.provider));
+        mApp2Provider1.provider = ComponentName.createRelative("app2", "provider1");
+        ReflectionHelpers.setField(mApp2Provider1, "providerInfo",
+                packageManager.addReceiverIfNotPresent(mApp2Provider1.provider));
+        mApp4Provider1.provider = ComponentName.createRelative("app4", "provider1");
+        ReflectionHelpers.setField(mApp4Provider1, "providerInfo",
+                packageManager.addReceiverIfNotPresent(mApp4Provider1.provider));
+        mApp4Provider2.provider = ComponentName.createRelative("app4", ".provider2");
+        ReflectionHelpers.setField(mApp4Provider2, "providerInfo",
+                packageManager.addReceiverIfNotPresent(mApp4Provider2.provider));
+        mApp5Provider1.provider = ComponentName.createRelative("app5", "provider1");
+        ReflectionHelpers.setField(mApp5Provider1, "providerInfo",
+                packageManager.addReceiverIfNotPresent(mApp5Provider1.provider));
+
+        ShadowAppWidgetManager shadowAppWidgetManager =
+                shadowOf(mContext.getSystemService(AppWidgetManager.class));
+        shadowAppWidgetManager.addInstalledProvider(mApp1Provider1);
+        shadowAppWidgetManager.addInstalledProvider(mApp1Provider2);
+        shadowAppWidgetManager.addInstalledProvider(mApp2Provider1);
+        shadowAppWidgetManager.addInstalledProvider(mApp4Provider1);
+        shadowAppWidgetManager.addInstalledProvider(mApp4Provider2);
+        shadowAppWidgetManager.addInstalledProvider(mApp5Provider1);
+
+        mModelHelper.getModel().addCallbacks(mCallback);
+
+        MODEL_EXECUTOR.post(() -> mModelHelper.getBgDataModel().widgetsModel.update(
+                LauncherAppState.getInstance(mContext), /* packageUser= */ null));
+        waitUntilIdle();
+    }
+
+
+    @Test
+    public void widgetsRecommendationRan_shouldOnlyReturnNotAddedWidgetsInAppPredictionOrder()
+            throws Exception {
+        // WHEN newPredicationTask is executed with app predication of 5 apps.
+        AppTarget app1 = new AppTarget(new AppTargetId("app1"), "app1", "className",
+                mUserHandle);
+        AppTarget app2 = new AppTarget(new AppTargetId("app2"), "app2", "className",
+                mUserHandle);
+        AppTarget app3 = new AppTarget(new AppTargetId("app3"), "app3", "className",
+                mUserHandle);
+        AppTarget app4 = new AppTarget(new AppTargetId("app4"), "app4", "className",
+                mUserHandle);
+        AppTarget app5 = new AppTarget(new AppTargetId("app5"), "app5", "className",
+                mUserHandle);
+        mModelHelper.executeTaskForTest(
+                newWidgetsPredicationTask(List.of(app5, app3, app2, app4, app1)))
+                .forEach(Runnable::run);
+
+        // THEN only 3 widgets are returned because
+        // 1. app5/provider1 & app4/provider1 have already been added to workspace. They are
+        //    excluded from the result.
+        // 2. app3 doesn't have a widget.
+        // 3. only 1 widget is picked from app1 because we only want to promote one widget per app.
+        List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
+                .stream()
+                .map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
+                .collect(Collectors.toList());
+        assertThat(recommendedWidgets).hasSize(3);
+        assertWidgetInfo(recommendedWidgets.get(0).info, mApp2Provider1);
+        assertWidgetInfo(recommendedWidgets.get(1).info, mApp4Provider2);
+        assertWidgetInfo(recommendedWidgets.get(2).info, mApp1Provider1);
+    }
+
+    private void assertWidgetInfo(
+            LauncherAppWidgetProviderInfo actual, AppWidgetProviderInfo expected) {
+        assertThat(actual.provider).isEqualTo(expected.provider);
+        assertThat(actual.getUser()).isEqualTo(expected.getProfile());
+    }
+
+    private void waitUntilIdle() {
+        shadowOf(MODEL_EXECUTOR.getLooper()).idle();
+        shadowOf(MAIN_EXECUTOR.getLooper()).idle();
+    }
+
+    private WidgetsPredictionUpdateTask newWidgetsPredicationTask(List<AppTarget> appTargets) {
+        return new WidgetsPredictionUpdateTask(
+                new PredictorState(CONTAINER_WIDGETS_PREDICTION, "test_widgets_prediction"),
+                appTargets);
+    }
+
+    private final class FakeBgDataModelCallback implements BgDataModel.Callbacks {
+
+        private FixedContainerItems mRecommendedWidgets = null;
+
+        @Override
+        public void bindExtraContainerItems(FixedContainerItems item) {
+            mRecommendedWidgets = item;
+        }
+
+        @Override
+        public int getPageToBindSynchronously() {
+            return 0;
+        }
+
+        @Override
+        public void clearPendingBinds() { }
+
+        @Override
+        public void startBinding() { }
+
+        @Override
+        public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
+
+        @Override
+        public void bindScreens(IntArray orderedScreenIds) { }
+
+        @Override
+        public void finishFirstPageBind(ViewOnDrawExecutor executor) { }
+
+        @Override
+        public void finishBindingItems(int pageBoundFirst) { }
+
+        @Override
+        public void preAddApps() { }
+
+        @Override
+        public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
+                ArrayList<ItemInfo> addAnimated) { }
+
+        @Override
+        public void bindIncrementalDownloadProgressUpdated(AppInfo app) { }
+
+        @Override
+        public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
+
+        @Override
+        public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
+
+        @Override
+        public void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
+
+        @Override
+        public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
+
+        @Override
+        public void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
+
+        @Override
+        public void onPageBoundSynchronously(int page) { }
+
+        @Override
+        public void executeOnNextDraw(ViewOnDrawExecutor executor) { }
+
+        @Override
+        public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) { }
+
+        @Override
+        public void bindAllApplications(AppInfo[] apps, int flags) { }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 5b30143..56d25f2 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -35,6 +35,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.proxy.ProxyActivityStarter;
@@ -62,6 +63,7 @@
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
+import java.util.List;
 import java.util.stream.Stream;
 
 /**
@@ -83,6 +85,8 @@
 
     private @Nullable TaskbarController mTaskbarController;
     private final TaskbarStateHandler mTaskbarStateHandler = new TaskbarStateHandler(this);
+    // Will be updated when dragging from taskbar.
+    private DragOptions mWorkspaceDragOptions = new DragOptions();
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -241,15 +245,12 @@
     }
 
     @Override
-    protected StateHandler<LauncherState>[] createStateHandlers() {
-        return new StateHandler[] {
-                getAllAppsController(),
-                getWorkspace(),
-                getDepthController(),
-                new RecentsViewStateController(this),
-                new BackButtonAlphaHandler(this),
-                getTaskbarStateHandler(),
-        };
+    protected void collectStateHandlers(List<StateHandler> out) {
+        super.collectStateHandlers(out);
+        out.add(getDepthController());
+        out.add(new RecentsViewStateController(this));
+        out.add(new BackButtonAlphaHandler(this));
+        out.add(getTaskbarStateHandler());
     }
 
     public DepthController getDepthController() {
@@ -270,6 +271,15 @@
     }
 
     @Override
+    public DragOptions getDefaultWorkspaceDragOptions() {
+        return mWorkspaceDragOptions;
+    }
+
+    public void setWorkspaceDragOptions(DragOptions dragOptions) {
+        mWorkspaceDragOptions = dragOptions;
+    }
+
+    @Override
     public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
         QuickstepAppTransitionManagerImpl appTransitionManager =
                 (QuickstepAppTransitionManagerImpl) getAppTransitionManager();
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index b570d55..83234e3 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -41,7 +41,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsSectionDecorator;
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
 import com.android.launcher3.anim.AlphaUpdateListener;
@@ -106,8 +105,6 @@
 
     private boolean mPredictionsEnabled = false;
 
-    AllAppsSectionDecorator.SectionDecorationHandler mDecorationHandler;
-
     @Nullable
     private List<ItemInfo> mPendingPredictedItems;
 
@@ -129,11 +126,6 @@
         mIconFullTextAlpha = Color.alpha(mIconTextColor);
         mIconCurrentTextAlpha = mIconFullTextAlpha;
 
-        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
-            mDecorationHandler = new AllAppsSectionDecorator.SectionDecorationHandler(getContext(),
-                    false, 0, true, true);
-        }
-
         updateVisibility();
     }
 
@@ -159,16 +151,6 @@
 
     @Override
     protected void dispatchDraw(Canvas canvas) {
-        if (mDecorationHandler != null) {
-            mDecorationHandler.reset();
-            int childrenCount = getChildCount();
-            for (int i = 0; i < childrenCount; i++) {
-                mDecorationHandler.extendBounds(getChildAt(i));
-            }
-            mDecorationHandler.onGroupDraw(canvas);
-            mDecorationHandler.onFocusDraw(canvas, getFocusedChild());
-            mLauncher.getAppsView().getActiveRecyclerView().invalidateItemDecorations();
-        }
         mFocusHelper.draw(canvas);
         super.dispatchDraw(canvas);
     }
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 0156e8f..f297343 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -107,7 +107,8 @@
         WorkspaceItemInfo dragItem = new WorkspaceItemInfo((WorkspaceItemInfo) v.getTag());
         v.setVisibility(View.INVISIBLE);
         mLauncher.getWorkspace().beginDragShared(
-                v, null, this, dragItem, new DragPreviewProvider(v), new DragOptions());
+                v, null, this, dragItem, new DragPreviewProvider(v),
+                mLauncher.getDefaultWorkspaceDragOptions());
         return true;
     };
 
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 2ea8bd2..b1b4d70 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 import static com.android.launcher3.Utilities.getDevicePrefs;
@@ -80,6 +81,8 @@
             new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
     private final PredictorState mHotseatState =
             new PredictorState(CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions");
+    private final PredictorState mWidgetsRecommendationState =
+            new PredictorState(CONTAINER_WIDGETS_PREDICTION, "widgets_prediction");
 
     private final InvariantDeviceProfile mIDP;
     private final AppEventProducer mAppEventProducer;
@@ -111,6 +114,9 @@
         mHotseatState.items.setItems(
                 mHotseatState.storage.read(mApp.getContext(), hotseatFactory, ums.allUsers::get));
         mDataModel.extraItems.put(CONTAINER_HOTSEAT_PREDICTION, mHotseatState.items);
+
+        // Widgets prediction isn't used frequently. And thus, it is not persisted on disk.
+        mDataModel.extraItems.put(CONTAINER_WIDGETS_PREDICTION, mWidgetsRecommendationState.items);
         mActive = true;
     }
 
@@ -161,6 +167,9 @@
         if (mAllAppsState.predictor != null) {
             mAllAppsState.predictor.requestPredictionUpdate();
         }
+        if (mWidgetsRecommendationState.predictor != null) {
+            mWidgetsRecommendationState.predictor.requestPredictionUpdate();
+        }
     }
 
     @Override
@@ -176,6 +185,7 @@
     private void destroyPredictors() {
         mAllAppsState.destroyPredictor();
         mHotseatState.destroyPredictor();
+        mWidgetsRecommendationState.destroyPredictor();
     }
 
     @WorkerThread
@@ -203,6 +213,12 @@
                         .setPredictedTargetCount(mIDP.numHotseatIcons)
                         .setExtras(convertDataModelToAppTargetBundle(context, mDataModel))
                         .build()));
+
+        registerWidgetsPredictor(apm.createAppPredictionSession(
+                new AppPredictionContext.Builder(context)
+                        .setUiSurface("widgets")
+                        .setPredictedTargetCount(mIDP.numColumns)
+                        .build()));
     }
 
     private void registerPredictor(PredictorState state, AppPredictor predictor) {
@@ -220,6 +236,20 @@
         mApp.getModel().enqueueModelUpdateTask(new PredictionUpdateTask(state, targets));
     }
 
+    private void registerWidgetsPredictor(AppPredictor predictor) {
+        mWidgetsRecommendationState.predictor = predictor;
+        mWidgetsRecommendationState.predictor.registerPredictionUpdates(
+                Executors.MODEL_EXECUTOR, targets -> {
+                    if (mWidgetsRecommendationState.setTargets(targets)) {
+                        // No diff, skip
+                        return;
+                    }
+                    mApp.getModel().enqueueModelUpdateTask(
+                            new WidgetsPredictionUpdateTask(mWidgetsRecommendationState, targets));
+                });
+        mWidgetsRecommendationState.predictor.requestPredictionUpdate();
+    }
+
     @Override
     public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
         if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
new file mode 100644
index 0000000..a29ac1a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 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 android.app.prediction.AppTarget;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/** Task to update model as a result of predicted widgets update */
+public final class WidgetsPredictionUpdateTask extends BaseModelUpdateTask {
+    private final PredictorState mPredictorState;
+    private final List<AppTarget> mTargets;
+
+    WidgetsPredictionUpdateTask(PredictorState predictorState, List<AppTarget> targets) {
+        mPredictorState = predictorState;
+        mTargets = targets;
+    }
+
+    /**
+     * Uses the app predication result to infer widgets that the user may want to use.
+     *
+     * <p>The algorithm uses the app prediction ranking to create a widgets ranking which only
+     * includes one widget per app and excludes widgets that have already been added to the
+     * workspace.
+     */
+    @Override
+    public void execute(LauncherAppState appState, BgDataModel dataModel, AllAppsList apps) {
+        Set<ComponentKey> widgetsInWorkspace = dataModel.appWidgets.stream().map(
+                widget -> new ComponentKey(widget.providerName, widget.user)).collect(
+                Collectors.toSet());
+        Map<PackageUserKey, List<WidgetItem>> allWidgets =
+                dataModel.widgetsModel.getAllWidgetsWithoutShortcuts();
+
+        ArrayList<ItemInfo> recommendedWidgetsInDescendingOrder = new ArrayList<>();
+        for (AppTarget app : mTargets) {
+            PackageUserKey packageUserKey = new PackageUserKey(app.getPackageName(), app.getUser());
+            if (allWidgets.containsKey(packageUserKey)) {
+                List<WidgetItem> notAddedWidgets = allWidgets.get(packageUserKey).stream()
+                        .filter(item ->
+                                !widgetsInWorkspace.contains(
+                                        new ComponentKey(item.componentName, item.user)))
+                        .collect(Collectors.toList());
+                if (notAddedWidgets.size() > 0) {
+                    // Even an apps have more than one widgets, we only include one widget.
+                    recommendedWidgetsInDescendingOrder.add(
+                            new PendingAddWidgetInfo(notAddedWidgets.get(0).widgetInfo));
+                }
+            }
+        }
+        FixedContainerItems fixedContainerItems = mPredictorState.items;
+        fixedContainerItems.items.clear();
+        fixedContainerItems.items.addAll(recommendedWidgetsInDescendingOrder);
+        bindExtraContainerItems(fixedContainerItems);
+
+        // Don't store widgets prediction to disk because it is not used frequently.
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 06372fe..8312b82 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -35,13 +35,14 @@
     private final DeviceProfile mDeviceProfile;
     private final LayoutInflater mLayoutInflater;
     private final TaskbarContainerView mTaskbarContainerView;
+    private final float mIconScale;
 
     public TaskbarActivityContext(BaseQuickstepLauncher launcher) {
         super(launcher);
         mDeviceProfile = launcher.getDeviceProfile().copy(this);
         float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size);
-        float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
-        mDeviceProfile.updateIconSize(iconScale, getResources());
+        mIconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
+        mDeviceProfile.updateIconSize(mIconScale, getResources());
 
         mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
 
@@ -75,4 +76,11 @@
     public Rect getFolderBoundingBox() {
         return mTaskbarContainerView.getFolderBoundingBox();
     }
+
+    /**
+     * @return The ratio of taskbar icon size vs normal workspace/hotseat icon size.
+     */
+    public float getTaskbarIconScale() {
+        return mIconScale;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
index ddd0d15..1e5e3e7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
@@ -97,7 +97,12 @@
             // to show a floating view like Folder. Thus, we set the contentInsets to be where
             // mTaskbarView is, since its position never changes and insets rather than overlays.
             int[] loc = mTempLoc;
+            float scale = mTaskbarView.getScaleX();
+            mTaskbarView.setScaleX(1);
+            mTaskbarView.setScaleY(1);
             mTaskbarView.getLocationInWindow(loc);
+            mTaskbarView.setScaleX(scale);
+            mTaskbarView.setScaleY(scale);
             insetsInfo.contentInsets.left = loc[0];
             insetsInfo.contentInsets.top = loc[1];
             insetsInfo.contentInsets.right = getWidth() - (loc[0] + mTaskbarView.getWidth());
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index 5dddaf3..544835c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -19,14 +19,18 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.app.ActivityOptions;
 import android.content.ComponentName;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
@@ -81,6 +85,9 @@
     // Contains all loaded Hotseat items.
     private ItemInfo[] mLatestLoadedHotseatItems;
 
+    private @Nullable Animator mAnimator;
+    private boolean mIsAnimatingToLauncher;
+
     public TaskbarController(BaseQuickstepLauncher launcher,
             TaskbarContainerView taskbarContainerView) {
         mLauncher = launcher;
@@ -164,7 +171,22 @@
 
             @Override
             public View.OnLongClickListener getItemOnLongClickListener() {
-                return mDragController::startDragOnLongClick;
+                return view -> {
+                    if (mLauncher.hasBeenResumed() && view.getTag() instanceof ItemInfo) {
+                        alignRealHotseatWithTaskbar();
+                        return mDragController.startWorkspaceDragOnLongClick(view);
+                    } else {
+                        return mDragController.startSystemDragOnLongClick(view);
+                    }
+                };
+            }
+
+            @Override
+            public int getEmptyHotseatViewVisibility() {
+                // When on the home screen, we want the empty hotseat views to take up their full
+                // space so that the others line up with the home screen hotseat.
+                return mLauncher.hasBeenResumed() || mIsAnimatingToLauncher
+                        ? View.INVISIBLE : View.GONE;
             }
         };
     }
@@ -207,6 +229,8 @@
         mTaskbarVisibilityController.init();
         mHotseatController.init();
         mRecentsController.init();
+
+        SCALE_PROPERTY.set(mTaskbarView, mLauncher.hasBeenResumed() ? getTaskbarScaleOnHome() : 1f);
     }
 
     private TaskbarStateHandlerCallbacks createTaskbarStateHandlerCallbacks() {
@@ -229,6 +253,10 @@
         mTaskbarVisibilityController.cleanup();
         mHotseatController.cleanup();
         mRecentsController.cleanup();
+
+        if (mAnimator != null) {
+            mAnimator.end();
+        }
     }
 
     private void removeFromWindowManager() {
@@ -271,13 +299,21 @@
      */
     public void onLauncherResumedOrPaused(boolean isResumed) {
         long duration = QuickstepAppTransitionManagerImpl.CONTENT_ALPHA_DURATION;
-        final Animator anim;
-        if (isResumed) {
-            anim = createAnimToLauncher(null, duration);
-        } else {
-            anim = createAnimToApp(duration);
+        if (mAnimator != null) {
+            mAnimator.cancel();
         }
-        anim.start();
+        if (isResumed) {
+            mAnimator = createAnimToLauncher(null, duration);
+        } else {
+            mAnimator = createAnimToApp(duration);
+        }
+        mAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mAnimator = null;
+            }
+        });
+        mAnimator.start();
     }
 
     /**
@@ -290,11 +326,38 @@
         if (toState != null) {
             mTaskbarStateHandler.setStateWithAnimation(toState, new StateAnimationConfig(), anim);
         }
+        anim.addFloat(mTaskbarView, SCALE_PROPERTY, mTaskbarView.getScaleX(),
+                getTaskbarScaleOnHome(), LINEAR);
+
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mIsAnimatingToLauncher = true;
+                mTaskbarView.updateHotseatItemsVisibility();
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mIsAnimatingToLauncher = false;
+            }
+        });
+
+        anim.addOnFrameCallback(this::alignRealHotseatWithTaskbar);
+
         return anim.buildAnim();
     }
 
     private Animator createAnimToApp(long duration) {
-        return mTaskbarVisibilityController.createAnimToBackgroundAlpha(1, duration);
+        PendingAnimation anim = new PendingAnimation(duration);
+        anim.add(mTaskbarVisibilityController.createAnimToBackgroundAlpha(1, duration));
+        anim.addFloat(mTaskbarView, SCALE_PROPERTY, mTaskbarView.getScaleX(), 1f, LINEAR);
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mTaskbarView.updateHotseatItemsVisibility();
+            }
+        });
+        return anim.buildAnim();
     }
 
     /**
@@ -378,6 +441,21 @@
     }
 
     /**
+     * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat.
+     */
+    public void alignRealHotseatWithTaskbar() {
+        Rect hotseatBounds = new Rect();
+        mTaskbarView.getHotseatBoundsAtScale(getTaskbarScaleOnHome()).roundOut(hotseatBounds);
+        mLauncher.getHotseat().setPadding(hotseatBounds.left, hotseatBounds.top,
+                mTaskbarView.getWidth() - hotseatBounds.right,
+                mTaskbarView.getHeight() - hotseatBounds.bottom);
+    }
+
+    private float getTaskbarScaleOnHome() {
+        return 1f / mTaskbarContainerView.getTaskbarActivityContext().getTaskbarIconScale();
+    }
+
+    /**
      * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size.
      */
     private void setTaskbarWindowFullscreen(boolean fullscreen) {
@@ -420,6 +498,7 @@
     protected interface TaskbarViewCallbacks {
         View.OnClickListener getItemOnClickListener();
         View.OnLongClickListener getItemOnLongClickListener();
+        int getEmptyHotseatViewVisibility();
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index baec899..f51e498 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
+import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ClipDescriptionCompat;
@@ -57,7 +58,7 @@
      * generate the ClipDescription and Intent.
      * @return Whether {@link View#startDragAndDrop} started successfully.
      */
-    protected boolean startDragOnLongClick(View view) {
+    protected boolean startSystemDragOnLongClick(View view) {
         if (!(view instanceof BubbleTextView)) {
             return false;
         }
@@ -125,6 +126,38 @@
     }
 
     /**
+     * Starts a drag and drop operation that controls a real Workspace (Hotseat) view.
+     * @param view The Taskbar item that was long clicked.
+     * @return Whether {@link View#startDragAndDrop} started successfully.
+     */
+    protected boolean startWorkspaceDragOnLongClick(View view) {
+        View.DragShadowBuilder transparentShadowBuilder = new View.DragShadowBuilder(view) {
+            private static final int ARBITRARY_SHADOW_SIZE = 10;
+
+            @Override
+            public void onDrawShadow(Canvas canvas) {
+            }
+
+            @Override
+            public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
+                outShadowSize.set(ARBITRARY_SHADOW_SIZE, ARBITRARY_SHADOW_SIZE);
+                outShadowTouchPoint.set(ARBITRARY_SHADOW_SIZE / 2, ARBITRARY_SHADOW_SIZE / 2);
+            }
+        };
+
+        TaskbarDragListener taskbarDragListener = new TaskbarDragListener(mLauncher,
+                (ItemInfo) view.getTag());
+        if (view.startDragAndDrop(new ClipData("", new String[] {taskbarDragListener.getMimeType()},
+                        new ClipData.Item("")),
+                transparentShadowBuilder, null /* localState */, View.DRAG_FLAG_GLOBAL)) {
+            view.setOnDragListener(getDraggedViewDragListener());
+            taskbarDragListener.init(mLauncher.getDragLayer());
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Hide the original Taskbar item while it is being dragged.
      */
     private View.OnDragListener getDraggedViewDragListener() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java
new file mode 100644
index 0000000..2bd5861
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2021 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.taskbar;
+
+import android.content.ClipDescription;
+import android.graphics.Point;
+import android.view.DragEvent;
+import android.view.View;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.model.data.ItemInfo;
+
+import java.util.UUID;
+
+/**
+ * Listens to system drag and drop events initated by the Taskbar, and forwards them to Launcher's
+ * internal DragController to move Hotseat items.
+ */
+public class TaskbarDragListener implements View.OnDragListener {
+
+    private static final String MIME_TYPE_PREFIX = "com.android.launcher3.taskbar.drag_and_drop/";
+
+    private final BaseQuickstepLauncher mLauncher;
+    private final ItemInfo mDraggedItem;
+    private final DragOptions mDragOptions;
+    // Randomly generated id used to verify the drag event.
+    private final String mId;
+
+    // Initialized in init().
+    DragLayer mDragLayer;
+
+    /**
+     * @param draggedItem The info of the item that was long clicked, which we will use to find
+     *                    the equivalent match on Hotseat to drag internally.
+     */
+    public TaskbarDragListener(BaseQuickstepLauncher launcher, ItemInfo draggedItem) {
+        mLauncher = launcher;
+        mDraggedItem = draggedItem;
+        mDragOptions = new DragOptions();
+        mDragOptions.simulatedDndStartPoint = new Point();
+        mId = UUID.randomUUID().toString();
+    }
+
+    protected void init(DragLayer dragLayer) {
+        mDragLayer = dragLayer;
+        mDragLayer.setOnDragListener(this);
+    }
+
+    private void cleanup() {
+        mDragLayer.setOnDragListener(null);
+        mLauncher.setWorkspaceDragOptions(new DragOptions());
+    }
+
+    /**
+     * Returns a randomly generated id used to verify the drag event.
+     */
+    protected String getMimeType() {
+        return MIME_TYPE_PREFIX + mId;
+    }
+
+    @Override
+    public boolean onDrag(View dragLayer, DragEvent dragEvent) {
+        ClipDescription clipDescription = dragEvent.getClipDescription();
+        if (dragEvent.getAction() == DragEvent.ACTION_DRAG_STARTED) {
+            if (clipDescription == null || !clipDescription.hasMimeType(getMimeType())) {
+                // We didn't initiate this drag, ignore.
+                cleanup();
+                return false;
+            }
+            View hotseatView = mLauncher.getHotseat().getFirstItemMatch(
+                    (info, view) -> info == mDraggedItem);
+            if (hotseatView == null) {
+                cleanup();
+                return false;
+            }
+            mDragOptions.simulatedDndStartPoint.set((int) dragEvent.getX(), (int) dragEvent.getY());
+            mLauncher.setWorkspaceDragOptions(mDragOptions);
+            hotseatView.performLongClick();
+        } else if (dragEvent.getAction() == DragEvent.ACTION_DRAG_ENDED) {
+            cleanup();
+        }
+        return mLauncher.getDragController().onDragEvent(dragEvent);
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 7a13b89..a729e77 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
+import android.graphics.Matrix;
 import android.graphics.RectF;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -35,6 +36,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -50,10 +52,12 @@
     private final ColorDrawable mBackgroundDrawable;
     private final int mItemMarginLeftRight;
     private final int mIconTouchSize;
+    private final boolean mIsRtl;
     private final int mTouchSlop;
     private final RectF mTempDelegateBounds = new RectF();
     private final RectF mDelegateSlopBounds = new RectF();
     private final int[] mTempOutLocation = new int[2];
+    private final Matrix mTempMatrix = new Matrix();
 
     // Initialized in TaskbarController constructor.
     private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
@@ -94,6 +98,7 @@
         mBackgroundDrawable = (ColorDrawable) getBackground();
         mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
         mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size);
+        mIsRtl = Utilities.isRtl(resources);
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
     }
 
@@ -131,7 +136,8 @@
      */
     protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
         for (int i = 0; i < hotseatItemInfos.length; i++) {
-            ItemInfo hotseatItemInfo = hotseatItemInfos[i];
+            ItemInfo hotseatItemInfo = hotseatItemInfos[!mIsRtl ? i
+                    : hotseatItemInfos.length - i - 1];
             int hotseatIndex = mHotseatStartIndex + i;
             View hotseatView = getChildAt(hotseatIndex);
 
@@ -176,25 +182,45 @@
                     && hotseatItemInfo instanceof WorkspaceItemInfo) {
                 ((BubbleTextView) hotseatView).applyFromWorkspaceItem(
                         (WorkspaceItemInfo) hotseatItemInfo);
-                hotseatView.setVisibility(VISIBLE);
                 hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
                 hotseatView.setOnLongClickListener(
                         mControllerCallbacks.getItemOnLongClickListener());
             } else if (isFolder) {
-                hotseatView.setVisibility(VISIBLE);
                 hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
                 hotseatView.setOnLongClickListener(
                         mControllerCallbacks.getItemOnLongClickListener());
             } else {
-                hotseatView.setVisibility(GONE);
                 hotseatView.setOnClickListener(null);
                 hotseatView.setOnLongClickListener(null);
+                hotseatView.setTag(null);
             }
+            updateHotseatItemVisibility(hotseatView);
         }
 
         updateHotseatRecentsDividerVisibility();
     }
 
+    protected void updateHotseatItemsVisibility() {
+        for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) {
+            updateHotseatItemVisibility(getChildAt(i));
+        }
+    }
+
+    private void updateHotseatItemVisibility(View hotseatView) {
+        if (hotseatView.getTag() != null) {
+            hotseatView.setVisibility(VISIBLE);
+        } else {
+            int oldVisibility = hotseatView.getVisibility();
+            int newVisibility = mControllerCallbacks.getEmptyHotseatViewVisibility();
+            hotseatView.setVisibility(newVisibility);
+            if (oldVisibility == GONE && newVisibility != GONE) {
+                // By default, the layout transition only runs when going to VISIBLE,
+                // but we want it to run when going to GONE to INVISIBLE as well.
+                getLayoutTransition().showChild(this, hotseatView, oldVisibility);
+            }
+        }
+    }
+
     private View addDivider(int dividerIndex) {
         View divider = inflate(R.layout.taskbar_divider);
         addView(divider, dividerIndex);
@@ -390,6 +416,35 @@
         return mIsDraggingItem;
     }
 
+    /**
+     * @return The bounding box of where the hotseat elements will be when we reach the given scale.
+     */
+    protected RectF getHotseatBoundsAtScale(float taskbarViewScale) {
+        View firstHotseatView = null, lastHotseatView = null;
+        for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) {
+            View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                if (firstHotseatView == null) {
+                    firstHotseatView = child;
+                }
+                lastHotseatView = child;
+            }
+        }
+        if (firstHotseatView == null || lastHotseatView == null) {
+            return new RectF();
+        }
+        View leftmostHotseatView = !mIsRtl ? firstHotseatView : lastHotseatView;
+        View rightmostHotseatView = !mIsRtl ? lastHotseatView : firstHotseatView;
+        RectF hotseatBounds = new RectF(
+                leftmostHotseatView.getLeft() - mItemMarginLeftRight,
+                leftmostHotseatView.getTop(),
+                rightmostHotseatView.getRight() + mItemMarginLeftRight,
+                rightmostHotseatView.getBottom());
+        mTempMatrix.setScale(taskbarViewScale, taskbarViewScale, getPivotX(), getPivotY());
+        mTempMatrix.mapRect(hotseatBounds);
+        return hotseatBounds;
+    }
+
     // FolderIconParent implemented methods.
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 98551fb..d330a68 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -228,17 +228,13 @@
      */
     public static class PredictedIconOutlineDrawing extends CellLayout.DelegatedCellDrawing {
 
-        private int mOffsetX;
-        private int mOffsetY;
-        private int mIconRadius;
-        private Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        private final PredictedAppIcon mIcon;
+        private final Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 
         public PredictedIconOutlineDrawing(int cellX, int cellY, PredictedAppIcon icon) {
             mDelegateCellX = cellX;
             mDelegateCellY = cellY;
-            mOffsetX = icon.getOutlineOffsetX();
-            mOffsetY = icon.getOutlineOffsetY();
-            mIconRadius = icon.mNormalizedIconRadius;
+            mIcon = icon;
             mOutlinePaint.setStyle(Paint.Style.FILL);
             mOutlinePaint.setColor(Color.argb(24, 245, 245, 245));
         }
@@ -248,7 +244,8 @@
          */
         @Override
         public void drawUnderItem(Canvas canvas) {
-            getShape().drawShape(canvas, mOffsetX, mOffsetY, mIconRadius, mOutlinePaint);
+            getShape().drawShape(canvas, mIcon.getOutlineOffsetX(), mIcon.getOutlineOffsetY(),
+                    mIcon.mNormalizedIconRadius, mOutlinePaint);
         }
 
         /**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 55dde45..b009629 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -218,6 +218,8 @@
                     .setPredictedApps(item.items);
         } else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
             mHotseatPredictionController.setPredictedItems(item);
+        } else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
+            getPopupDataProvider().setRecommendedWidgets(item.items);
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 69b8aca..473fe2d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -18,6 +18,7 @@
 import static android.view.View.VISIBLE;
 
 import static com.android.launcher3.LauncherState.HINT_STATE;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.WorkspaceStateTransitionAnimation.getSpringScaleAnimator;
@@ -32,6 +33,7 @@
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
@@ -48,7 +50,6 @@
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.Hotseat;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.AllAppsContainerView;
@@ -62,7 +63,7 @@
  * Animation factory for quickstep specific transitions
  */
 public class QuickstepAtomicAnimationFactory extends
-        RecentsAtomicAnimationFactory<Launcher, LauncherState> {
+        RecentsAtomicAnimationFactory<QuickstepLauncher, LauncherState> {
 
     // Scale recents takes before animating in
     private static final float RECENTS_PREPARE_SCALE = 1.33f;
@@ -149,6 +150,17 @@
                 mHintToNormalDuration = (int) va.getDuration();
             }
             config.duration = Math.max(config.duration, mHintToNormalDuration);
+        } else if (mActivity.getTaskbarController() != null)  {
+            boolean wasHotseatVisible = fromState.areElementsVisible(mActivity, HOTSEAT_ICONS);
+            boolean isHotseatVisible = toState.areElementsVisible(mActivity, HOTSEAT_ICONS);
+            if (wasHotseatVisible || isHotseatVisible) {
+                config.setInterpolator(ANIM_TASKBAR_FADE, INSTANT);
+                config.setInterpolator(ANIM_HOTSEAT_FADE, INSTANT);
+
+                if (isHotseatVisible) {
+                    mActivity.getTaskbarController().alignRealHotseatWithTaskbar();
+                }
+            }
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 6b9c340..a990f3e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -24,8 +24,8 @@
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.animation.ValueAnimator;
@@ -145,7 +145,7 @@
                     OverviewScrim.SCRIM_MULTIPLIER, OVERVIEW_TO_HOME_SCRIM_MULTIPLIER,
                     PULLBACK_INTERPOLATOR);
 
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (LIVE_TILE.get()) {
                 builder.addOnFrameCallback(recentsView::redrawLiveTile);
             }
 
@@ -194,7 +194,7 @@
         boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS
                 || (velocity < 0 && fling);
         if (success) {
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (LIVE_TILE.get()) {
                 RecentsView recentsView = mLauncher.getOverviewPanel();
                 recentsView.switchToScreenshot(null,
                         () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index c60e257..facfb9d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -126,26 +126,11 @@
 
     @Override
     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "PortraitStatesTouchController.getTargetState");
-        }
         if (fromState == ALL_APPS && !isDragTowardPositive) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
-                        "PortraitStatesTouchController.getTargetState 1");
-            }
             return NORMAL;
         } else if (fromState == OVERVIEW) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
-                        "PortraitStatesTouchController.getTargetState 2");
-            }
             return isDragTowardPositive ? OVERVIEW : NORMAL;
         } else if (fromState == NORMAL && isDragTowardPositive) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
-                        "PortraitStatesTouchController.getTargetState 3");
-            }
             return ALL_APPS;
         }
         return fromState;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 1bd7fe6..e0c041e 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -25,7 +25,7 @@
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
@@ -45,6 +45,7 @@
 import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
 import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
@@ -79,7 +80,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
@@ -104,7 +104,6 @@
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.SwipePipToHomeAnimator;
 import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.views.LiveTileOverlay;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -318,7 +317,7 @@
         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_FINISH_WITH_NO_END,
                 this::notifyTransitionCancelled);
 
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+        if (!LIVE_TILE.get()) {
             mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
                             | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
                     (b) -> mRecentsView.setRunningTaskHidden(!b));
@@ -353,7 +352,6 @@
 
         mRecentsView = activity.getOverviewPanel();
         mRecentsView.setOnPageTransitionEndCallback(null);
-        addLiveTileOverlay();
 
         mStateCallback.setState(STATE_LAUNCHER_PRESENT);
         if (alreadyOnHome) {
@@ -459,7 +457,7 @@
     }
 
     private void onDeferredActivityLaunch() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+        if (LIVE_TILE.get()) {
             mActivityInterface.switchRunningTaskViewToScreenshot(
                     null, () -> {
                         mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
@@ -918,26 +916,15 @@
                 isFling, isCancel);
         float endShift = endTarget.isLauncher ? 1 : 0;
         final float startShift;
-        Interpolator interpolator = DEACCEL;
         if (!isFling) {
             long expectedDuration = Math.abs(Math.round((endShift - currentShift)
                     * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
             duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
             startShift = currentShift;
-            interpolator = endTarget == RECENTS ? ACCEL_DEACCEL : DEACCEL;
         } else {
             startShift = Utilities.boundToRange(currentShift - velocity.y
                     * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
             if (mTransitionDragLength > 0) {
-                if (endTarget == RECENTS && !mDeviceState.isFullyGesturalNavMode()) {
-                    Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
-                            startShift, endShift, endShift, endVelocity,
-                            mTransitionDragLength, mContext);
-                    endShift = overshoot.end;
-                    interpolator = overshoot.interpolator;
-                    duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
-                            MAX_SWIPE_DURATION);
-                } else {
                     float distanceToTravel = (endShift - currentShift) * mTransitionDragLength;
 
                     // we want the page's snap velocity to approximately match the velocity at
@@ -945,13 +932,11 @@
                     // derivative of the scroll interpolator at zero, ie. 2.
                     long baseDuration = Math.round(Math.abs(distanceToTravel / velocity.y));
                     duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
-
-                    if (endTarget == RECENTS) {
-                        interpolator = ACCEL_DEACCEL;
-                    }
-                }
             }
         }
+        Interpolator interpolator =
+                endTarget == RECENTS ? (mDp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()
+                        ? ACCEL_DEACCEL : OVERSHOOT_1_2) : DEACCEL;
 
         if (endTarget.isLauncher) {
             mInputConsumerProxy.enable();
@@ -1241,13 +1226,6 @@
         });
         anim.addAnimatorListener(new AnimationSuccessListener() {
             @Override
-            public void onAnimationStart(Animator animation) {
-                if (mActivity != null) {
-                    removeLiveTileOverlay();
-                }
-            }
-
-            @Override
             public void onAnimationSuccess(Animator animator) {
                 if (mRecentsView != null) {
                     mRecentsView.post(mRecentsView::resetTaskVisuals);
@@ -1268,7 +1246,7 @@
             // In the off chance that the gesture ends before Launcher is started, we should clear
             // the callback here so that it doesn't update with the wrong state
             mActivity.clearRunOnceOnStartCallback();
-            resetLauncherListenersAndOverlays();
+            resetLauncherListeners();
         }
         if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) {
             cancelCurrentAnimation();
@@ -1351,7 +1329,7 @@
         endLauncherTransitionController();
 
         mRecentsView.onGestureAnimationEnd();
-        resetLauncherListenersAndOverlays();
+        resetLauncherListeners();
     }
 
     private void endLauncherTransitionController() {
@@ -1364,13 +1342,12 @@
         }
     }
 
-    private void resetLauncherListenersAndOverlays() {
+    private void resetLauncherListeners() {
         // Reset the callback for deferred activity launches
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+        if (!LIVE_TILE.get()) {
             mActivityInterface.setOnDeferredActivityLaunchCallback(null);
         }
         mActivity.getRootView().setOnApplyWindowInsetsListener(null);
-        removeLiveTileOverlay();
     }
 
     private void notifyTransitionCancelled() {
@@ -1387,7 +1364,7 @@
 
     protected void switchToScreenshot() {
         final int runningTaskId = mGestureState.getRunningTaskId();
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+        if (LIVE_TILE.get()) {
             if (mRecentsAnimationController != null) {
                 mRecentsAnimationController.getController().setWillFinishToHome(true);
                 // Update the screenshot of the task
@@ -1456,7 +1433,7 @@
     }
 
     private void finishCurrentTransitionToRecents() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+        if (LIVE_TILE.get()) {
             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
         } else if (!hasTargets() || mRecentsAnimationController == null) {
             // If there are no targets or the animation not started, then there is nothing to finish
@@ -1517,7 +1494,7 @@
         endLauncherTransitionController();
         mActivityInterface.onSwipeUpToRecentsComplete();
         mRecentsView.onSwipeUpAnimationSuccess();
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+        if (LIVE_TILE.get()) {
             mTaskAnimationManager.setLaunchOtherTaskInLiveTileModeHandler(
                     appearedTaskTarget -> {
                         RemoteAnimationTargetCompat[] apps = Arrays.copyOf(
@@ -1594,17 +1571,6 @@
         anim.start();
     }
 
-    private void addLiveTileOverlay() {
-        if (LiveTileOverlay.INSTANCE.attach(mActivity.getRootView().getOverlay())) {
-            mRecentsView.setLiveTileOverlayAttached(true);
-        }
-    }
-
-    private void removeLiveTileOverlay() {
-        LiveTileOverlay.INSTANCE.detach(mActivity.getRootView().getOverlay());
-        mRecentsView.setLiveTileOverlayAttached(false);
-    }
-
     private static boolean isNotInRecents(RemoteAnimationTargetCompat app) {
         return app.isNotInRecents
                 || app.activityType == ACTIVITY_TYPE_HOME;
@@ -1765,13 +1731,6 @@
             }
             mTaskViewSimulator.apply(mTransformParams);
         }
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mRecentsAnimationTargets != null) {
-            LiveTileOverlay.INSTANCE.update(
-                    mTaskViewSimulator.getCurrentRect(),
-                    mTaskViewSimulator.getCurrentCornerRadius());
-            LiveTileOverlay.INSTANCE.setRotation(
-                    mTaskViewSimulator.getOrientationState().getDisplayRotation());
-        }
         ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
     }
 
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 8b0d782..3f3e5ad 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -187,9 +187,6 @@
 
     @Override
     public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "switchToRecentsIfVisible");
-        }
         Launcher launcher = getVisibleLauncher();
         if (launcher == null) {
             return false;
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 6a32dc4..a749f9a 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -273,7 +273,6 @@
         Point size = display.realSize;
         int rotation = display.rotation;
         int touchHeight = mNavBarGesturalHeight;
-        int largerGesturalHeight = mNavBarLargerGesturalHeight;
         OrientationRectF orientationRectF =
                 new OrientationRectF(0, 0, size.x, size.y, rotation);
         if (mMode == SysUINavigationMode.Mode.NO_BUTTON) {
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index 65847f1..192738f 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep;
 
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
+
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -59,6 +61,8 @@
 
         super.init(context);
 
+        LIVE_TILE.initialize(context);
+
         // Elevate GPU priority for Quickstep and Remote animations.
         ThreadedRendererCompat.setContextPriority(
                 ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 1c5dc4c..2f1538b 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -22,10 +22,12 @@
 import android.app.ActivityManager;
 import android.os.Build;
 import android.os.Process;
+import android.util.Log;
 import android.util.SparseBooleanArray;
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -51,6 +53,9 @@
 
     // The list change id, increments as the task list changes in the system
     private int mChangeId;
+    // Whether we are currently updating the tasks in the background (up to when the result is
+    // posted back on the main thread)
+    private boolean mLoadingTasksInBackground;
 
     private TaskLoadResult mResultsBg = INVALID_RESULT;
     private TaskLoadResult mResultsUi = INVALID_RESULT;
@@ -64,6 +69,11 @@
         mActivityManagerWrapper.registerTaskStackListener(this);
     }
 
+    @VisibleForTesting
+    public boolean isLoadingTasksInBackground() {
+        return mLoadingTasksInBackground;
+    }
+
     /**
      * Fetches the task keys skipping any local cache.
      */
@@ -83,6 +93,10 @@
      * @return The change id of the current task list
      */
     public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.GET_RECENTS_FAILED, "getTasks: keysOnly=" + loadKeysOnly
+                    + " callback=" + callback);
+        }
         final int requestLoadId = mChangeId;
         if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) {
             // The list is up to date, send the callback on the next frame,
@@ -90,22 +104,38 @@
             if (callback != null) {
                 // Copy synchronously as the changeId might change by next frame
                 ArrayList<Task> result = copyOf(mResultsUi);
-                mMainThreadExecutor.post(() -> callback.accept(result));
+                mMainThreadExecutor.post(() -> {
+                    if (TestProtocol.sDebugTracing) {
+                        Log.d(TestProtocol.GET_RECENTS_FAILED, "getTasks: no new tasks");
+                    }
+                    callback.accept(result);
+                });
             }
 
             return requestLoadId;
         }
 
         // Kick off task loading in the background
+        mLoadingTasksInBackground = true;
         UI_HELPER_EXECUTOR.execute(() -> {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.GET_RECENTS_FAILED, "getTasks: loading in bg start");
+            }
             if (!mResultsBg.isValidForRequest(requestLoadId, loadKeysOnly)) {
                 mResultsBg = loadTasksInBackground(Integer.MAX_VALUE, requestLoadId, loadKeysOnly);
             }
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.GET_RECENTS_FAILED, "getTasks: loading in bg end");
+            }
             TaskLoadResult loadResult = mResultsBg;
             mMainThreadExecutor.execute(() -> {
+                mLoadingTasksInBackground = false;
                 mResultsUi = loadResult;
                 if (callback != null) {
                     ArrayList<Task> result = copyOf(mResultsUi);
+                    if (TestProtocol.sDebugTracing) {
+                        Log.d(TestProtocol.GET_RECENTS_FAILED, "getTasks: callback w/ bg results");
+                    }
                     callback.accept(result);
                 }
             });
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 8aa0842..1b2fd41 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -71,6 +71,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.List;
 
 /**
  * A recents activity that shows the recently launched tasks as swipable task cards.
@@ -317,8 +318,8 @@
     }
 
     @Override
-    protected StateHandler<RecentsState>[] createStateHandlers() {
-        return new StateHandler[] { new FallbackRecentsStateController(this) };
+    protected void collectStateHandlers(List<StateHandler> out) {
+        out.add(new FallbackRecentsStateController(this));
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index d47217b..ba24e6a 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -28,6 +28,8 @@
 import android.os.Process;
 import android.os.UserHandle;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.util.Executors.SimpleThreadFactory;
 import com.android.launcher3.util.MainThreadInitializedObject;
@@ -102,6 +104,14 @@
     }
 
     /**
+     * @return Whether the task list is currently updating in the background
+     */
+    @VisibleForTesting
+    public boolean isLoadingTasksInBackground() {
+        return mTaskList.isLoadingTasksInBackground();
+    }
+
+    /**
      * Finds and returns the task key associated with the given task id.
      *
      * @param callback The callback to receive the task key if it is found or null. This is always
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index ca73041..f4b8b62 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -94,13 +94,7 @@
         mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength(
                 dp, mContext, TEMP_RECT,
                 mTaskViewSimulator.getOrientationState().getOrientationHandler());
-
-        if (mDeviceState.isFullyGesturalNavMode()) {
-            // We can drag all the way to the top of the screen.
-            mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
-        } else {
-            mDragLengthFactor = 1 + AnimatorControllerWithResistance.TWO_BUTTON_EXTRA_DRAG_FACTOR;
-        }
+        mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
 
         PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2);
         mTaskViewSimulator.addAppToOverviewAnim(pa, LINEAR);
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 64d05e1..5668817 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -21,11 +21,11 @@
 import android.app.PictureInPictureParams;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.graphics.Bitmap;
 import android.graphics.Insets;
 import android.graphics.Rect;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
@@ -37,6 +37,7 @@
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.shared.recents.ISplitScreenListener;
+import com.android.systemui.shared.recents.IStartingWindowListener;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.RemoteTransitionCompat;
@@ -516,14 +517,40 @@
     }
 
     @Override
-    public void startIntent(PendingIntent intent, int stage, int position, Bundle options) {
+    public void startIntent(PendingIntent intent, Intent fillInIntent, int stage,
+            int position, Bundle options) {
         if (mSystemUiProxy != null) {
             try {
-                mSystemUiProxy.startIntent(intent, stage, position, options);
+                mSystemUiProxy.startIntent(intent, fillInIntent, stage, position,
+                        options);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startIntent");
             }
         }
     }
 
+    @Override
+    public void removeFromSideStage(int taskId) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.removeFromSideStage(taskId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call removeFromSideStage");
+            }
+        }
+    }
+
+    /**
+     * Sets listener to get callbacks when launching a task.
+     */
+    @Override
+    public void setStartingWindowListener(IStartingWindowListener listener) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.setStartingWindowListener(listener);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call setStartingWindowListener", e);
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 0d2c42e..8636130 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -18,7 +18,7 @@
 
 import static android.view.Surface.ROTATION_0;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL;
 import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
 
@@ -163,7 +163,7 @@
          * @param callback callback to run, after switching to screenshot
          */
         public void endLiveTileMode(@NonNull Runnable callback) {
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (LIVE_TILE.get()) {
                 RecentsView recentsView = mThumbnailView.getTaskView().getRecentsView();
                 recentsView.switchToScreenshot(
                         () -> recentsView.finishRecentsAnimation(true /* toRecents */, callback));
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index cd4f52f..17822e6 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -23,8 +23,8 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
@@ -139,7 +139,7 @@
         boolean isRunningTask = v.isRunningTask();
         TransformParams params = null;
         TaskViewSimulator tsv = null;
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
+        if (LIVE_TILE.get() && isRunningTask) {
             params = v.getRecentsView().getLiveTileParams();
             tsv = v.getRecentsView().getLiveTileTaskViewSimulator();
         }
@@ -159,8 +159,7 @@
         boolean isQuickSwitch = v.isEndQuickswitchCuj();
         v.setEndQuickswitchCuj(false);
 
-        boolean inLiveTileMode =
-                ENABLE_QUICKSTEP_LIVE_TILE.get() && v.getRecentsView().getRunningTaskIndex() != -1;
+        boolean inLiveTileMode = LIVE_TILE.get() && v.getRecentsView().getRunningTaskIndex() != -1;
         final RemoteAnimationTargets targets =
                 new RemoteAnimationTargets(appTargets, wallpaperTargets,
                         inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
@@ -181,6 +180,7 @@
         boolean parallaxCenterAndAdjacentTask =
                 taskIndex != recentsView.getCurrentPage() && !(dp.isTablet
                         && FeatureFlags.ENABLE_OVERVIEW_GRID.get());
+        float gridTranslationSecondary = recentsView.getGridTranslationSecondary(taskIndex);
         int startScroll = recentsView.getScrollOffset(taskIndex);
 
         TaskViewSimulator topMostSimulator = null;
@@ -197,6 +197,8 @@
             tsv.setPreview(targets.apps[targets.apps.length - 1]);
             tsv.fullScreenProgress.value = 0;
             tsv.recentsViewScale.value = 1;
+            tsv.gridProgress.value = 1;
+            tsv.gridTranslationSecondary.value = gridTranslationSecondary;
             tsv.setScroll(startScroll);
 
             // Fade in the task during the initial 20% of the animation
@@ -209,6 +211,7 @@
                     AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
             out.setFloat(tsv.recentsViewScale,
                     AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
+            out.setFloat(tsv.gridProgress, AnimatedFloat.VALUE, 0, TOUCH_RESPONSE_INTERPOLATOR);
             out.setInt(tsv, TaskViewSimulator.SCROLL, 0, TOUCH_RESPONSE_INTERPOLATOR);
 
             TaskViewSimulator finalTsv = tsv;
@@ -341,7 +344,7 @@
             };
         }
         pa.add(launcherAnim);
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1) {
+        if (LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1) {
             pa.addOnFrameCallback(recentsView::redrawLiveTile);
         }
         anim.play(pa.buildAnim());
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index e4c8b6f..fc805d0 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -21,9 +21,9 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.config.FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
 
@@ -290,7 +290,13 @@
 
     private void initInputMonitor() {
         disposeEventHandlers();
-        if (mDeviceState.isButtonNavMode() || !SystemUiProxy.INSTANCE.get(this).isActive()) {
+
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.TIS_NO_EVENTS, "initInputMonitor: isButtonMode? "
+                    + mDeviceState.isButtonNavMode());
+        }
+
+        if (mDeviceState.isButtonNavMode()) {
             return;
         }
 
@@ -681,8 +687,7 @@
                     runningComponent != null && runningComponent.equals(homeComponent);
         }
 
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()
-                && gestureState.getActivityInterface().isInLiveTileMode()) {
+        if (LIVE_TILE.get() && gestureState.getActivityInterface().isInLiveTileMode()) {
             return createOverviewInputConsumer(
                     previousGestureState, gestureState, event, forceOverviewInputConsumer);
         } else if (gestureState.getRunningTask() == null) {
@@ -738,8 +743,7 @@
                 || previousGestureState.isRunningAnimationToLauncher()
                 || (ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
                     && forceOverviewInputConsumer)
-                || (ENABLE_QUICKSTEP_LIVE_TILE.get())
-                    && gestureState.getActivityInterface().isInLiveTileMode()) {
+                || (LIVE_TILE.get()) && gestureState.getActivityInterface().isInLiveTileMode()) {
             return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat,
                     false /* startingInActivityBounds */);
         } else {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 8f2356c..13f6137 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -24,9 +24,11 @@
 import android.content.Context;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.util.Log;
 
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.quickstep.FallbackActivityInterface;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.RecentsActivity;
@@ -120,6 +122,10 @@
         // as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
         // track the index of the next task appropriately, as if we are switching on any other app.
         if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == mRunningTaskId && !tasks.isEmpty()) {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.GET_RECENTS_FAILED,
+                        "FallbackRecentsView.applyLoadPlan: running task is home");
+            }
             // Check if the task list has running task
             boolean found = false;
             for (Task t : tasks) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
index b10bdde..cd69cf1 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
@@ -21,14 +21,18 @@
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
 
+import static com.android.launcher3.ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE;
 import static com.android.launcher3.Utilities.squaredHypot;
 
 import android.content.Context;
+import android.graphics.Point;
 import android.graphics.PointF;
 import android.view.MotionEvent;
 
 import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.SystemUiProxy;
@@ -45,11 +49,15 @@
     private static final int ANGLE_MIN = 30;
 
     private final Context mContext;
+    private final DisplayController.DisplayHolder mDisplayHolder;
+    private final Point mDisplaySize;
     private final RecentsAnimationDeviceState mDeviceState;
 
     private final float mDragDistThreshold;
     private final float mSquaredSlop;
 
+    private final int mNavBarSize;
+
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
 
@@ -60,10 +68,14 @@
             InputConsumer delegate, InputMonitorCompat inputMonitor) {
         super(delegate, inputMonitor);
         mContext = context;
+        mDisplayHolder = DisplayController.getDefaultDisplay(mContext);
         mDeviceState = deviceState;
         mDragDistThreshold = context.getResources().getDimensionPixelSize(
                 R.dimen.gestures_onehanded_drag_threshold);
         mSquaredSlop = Utilities.squaredTouchSlop(context);
+        mDisplaySize = mDisplayHolder.getInfo().realSize;
+        mNavBarSize = ResourceUtils.getNavbarSize(NAVBAR_BOTTOM_GESTURE_SIZE,
+                mContext.getResources());
     }
 
     @Override
@@ -96,7 +108,8 @@
                                 mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))
                                 || (mDeviceState.isOneHandedModeActive() && isValidExitAngle(
                                 mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))) {
-                            mPassedSlop = true;
+                            // To avoid mis-trigger when motion not touch system gesture region.
+                            mPassedSlop = isInSystemGestureRegion(mLastPos);
                             setActive(ev);
                         } else {
                             mState = STATE_DELEGATE_ACTIVE;
@@ -154,6 +167,11 @@
         SystemUiProxy.INSTANCE.get(mContext).stopOneHandedMode();
     }
 
+    private boolean isInSystemGestureRegion(PointF lastPos) {
+        final int navBarUpperBound = mDisplaySize.y - mNavBarSize;
+        return mDeviceState.isGesturalNavMode() && lastPos.y > navBarUpperBound;
+    }
+
     private boolean isValidStartAngle(float deltaX, float deltaY) {
         final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
         return angle > -(ANGLE_MAX) && angle < -(ANGLE_MIN);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index aad70c4..cee3363 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -15,7 +15,7 @@
  */
 package com.android.quickstep.inputconsumers;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.view.KeyEvent;
@@ -99,7 +99,7 @@
 
     @Override
     public void onKeyEvent(KeyEvent ev) {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+        if (LIVE_TILE.get()) {
             mActivity.dispatchKeyEvent(ev);
         }
     }
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index a762cb7..66c24c8 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -31,6 +31,7 @@
 
 import android.content.Context;
 import android.util.Log;
+import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.WorkerThread;
@@ -55,6 +56,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.LogConfig;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.SysUiStatsLog;
 
 import java.util.Optional;
@@ -94,7 +96,7 @@
 
     @Override
     protected StatsLogger createLogger() {
-        return new StatsCompatLogger();
+        return new StatsCompatLogger(mContext);
     }
 
     /**
@@ -136,6 +138,7 @@
 
         private static final ItemInfo DEFAULT_ITEM_INFO = new ItemInfo();
 
+        private Context mContext;
         private ItemInfo mItemInfo = DEFAULT_ITEM_INFO;
         private InstanceId mInstanceId = DEFAULT_INSTANCE_ID;
         private OptionalInt mRank = OptionalInt.empty();
@@ -147,6 +150,10 @@
         private Optional<String> mEditText = Optional.empty();
         private SliceItem mSliceItem;
 
+        StatsCompatLogger(Context context) {
+            mContext = context;
+        }
+
         @Override
         public StatsLogger withItemInfo(ItemInfo itemInfo) {
             if (mContainerInfo.isPresent()) {
@@ -220,7 +227,6 @@
             if (!Utilities.ATLEAST_R) {
                 return;
             }
-
             LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
 
             if (mSliceItem != null) {
@@ -256,6 +262,26 @@
             }
         }
 
+        @Override
+        public void sendToInteractionJankMonitor(EventEnum event, View view) {
+            if (!(event instanceof LauncherEvent)) {
+                return;
+            }
+            switch ((LauncherEvent) event) {
+                case LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN:
+                    InteractionJankMonitorWrapper.begin(
+                            view,
+                            InteractionJankMonitorWrapper.CUJ_ALL_APPS_SCROLL);
+                    break;
+                case LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END:
+                    InteractionJankMonitorWrapper.end(
+                            InteractionJankMonitorWrapper.CUJ_ALL_APPS_SCROLL);
+                    break;
+                default:
+                    break;
+            }
+        }
+
         private LauncherAtom.ItemInfo applyOverwrites(LauncherAtom.ItemInfo atomInfo) {
             LauncherAtom.ItemInfo.Builder itemInfoBuilder = atomInfo.toBuilder();
 
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index deb70e0..7f94839 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
@@ -38,7 +37,6 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.LauncherActivityInterface;
-import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -49,12 +47,6 @@
  */
 public class AnimatorControllerWithResistance {
 
-    /**
-     * How much farther we can drag past overview in 2-button mode, as a factor of the distance
-     * it takes to drag from an app to overview.
-     */
-    public static final float TWO_BUTTON_EXTRA_DRAG_FACTOR = 0.25f;
-
     private enum RecentsResistanceParams {
         FROM_APP(0.75f, 0.5f, 1f),
         FROM_OVERVIEW(1f, 0.75f, 0.5f);
@@ -161,12 +153,6 @@
         LauncherActivityInterface.INSTANCE.calculateTaskSize(params.context, params.dp, startRect,
                 orientationHandler);
         long distanceToCover = startRect.bottom;
-        boolean isTwoButtonMode = SysUINavigationMode.getMode(params.context) == TWO_BUTTONS;
-        if (isTwoButtonMode) {
-            // We can only drag a small distance past overview, not to the top of the screen.
-            distanceToCover = (long)
-                    ((params.dp.heightPx - startRect.bottom) * TWO_BUTTON_EXTRA_DRAG_FACTOR);
-        }
         PendingAnimation resistAnim = params.resistAnim != null
                 ? params.resistAnim
                 : new PendingAnimation(distanceToCover * 2);
@@ -178,43 +164,35 @@
                 / (params.dp.heightPx - startRect.bottom);
         // This is what the scale would be at the end of the drag if we didn't apply resistance.
         float endScale = params.startScale - prevScaleRate * distanceToCover;
-        final TimeInterpolator scaleInterpolator;
-        if (isTwoButtonMode) {
-            // We are bounded by the distance of the drag, so we don't need to apply resistance.
-            scaleInterpolator = LINEAR;
-        } else {
-            // Create an interpolator that resists the scale so the scale doesn't get smaller than
-            // RECENTS_SCALE_MAX_RESIST.
-            float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist,
-                    params.startScale, endScale);
-            float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist,
-                    params.startScale, endScale);
-            scaleInterpolator = t -> {
-                if (t < startResist) {
-                    return t;
-                }
-                float resistProgress = Utilities.getProgress(t, startResist, 1);
-                resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress);
-                return startResist + resistProgress * (maxResist - startResist);
-            };
-        }
+        // Create an interpolator that resists the scale so the scale doesn't get smaller than
+        // RECENTS_SCALE_MAX_RESIST.
+        float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist,
+                params.startScale, endScale);
+        float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist,
+                params.startScale, endScale);
+        final TimeInterpolator scaleInterpolator = t -> {
+            if (t < startResist) {
+                return t;
+            }
+            float resistProgress = Utilities.getProgress(t, startResist, 1);
+            resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress);
+            return startResist + resistProgress * (maxResist - startResist);
+        };
         resistAnim.addFloat(params.scaleTarget, params.scaleProperty, params.startScale, endScale,
                 scaleInterpolator);
 
-        if (!isTwoButtonMode) {
-            // Compute where the task view would be based on the end scale, if we didn't translate.
-            RectF endRectF = new RectF(startRect);
-            Matrix temp = new Matrix();
-            temp.setScale(params.resistanceParams.scaleMaxResist,
-                    params.resistanceParams.scaleMaxResist, pivot.x, pivot.y);
-            temp.mapRect(endRectF);
-            // Translate such that the task view touches the top of the screen when drag does.
-            float endTranslation = endRectF.top
-                    * orientationHandler.getSecondaryTranslationDirectionFactor()
-                    * params.resistanceParams.translationFactor;
-            resistAnim.addFloat(params.translationTarget, params.translationProperty,
-                    params.startTranslation, endTranslation, RECENTS_TRANSLATE_RESIST_INTERPOLATOR);
-        }
+        // Compute where the task view would be based on the end scale.
+        RectF endRectF = new RectF(startRect);
+        Matrix temp = new Matrix();
+        temp.setScale(params.resistanceParams.scaleMaxResist,
+                params.resistanceParams.scaleMaxResist, pivot.x, pivot.y);
+        temp.mapRect(endRectF);
+        // Translate such that the task view touches the top of the screen when drag does.
+        float endTranslation = endRectF.top
+                * orientationHandler.getSecondaryTranslationDirectionFactor()
+                * params.resistanceParams.translationFactor;
+        resistAnim.addFloat(params.translationTarget, params.translationProperty,
+                params.startTranslation, endTranslation, RECENTS_TRANSLATE_RESIST_INTERPOLATOR);
 
         return resistAnim;
     }
diff --git a/quickstep/src/com/android/quickstep/util/NavigationModeFeatureFlag.java b/quickstep/src/com/android/quickstep/util/NavigationModeFeatureFlag.java
new file mode 100644
index 0000000..60c7add
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/NavigationModeFeatureFlag.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+
+import android.content.Context;
+
+import com.android.quickstep.SysUINavigationMode;
+
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/** A feature flag that listens to navigation mode changes. */
+public class NavigationModeFeatureFlag implements
+        SysUINavigationMode.NavigationModeChangeListener {
+
+    public static final NavigationModeFeatureFlag LIVE_TILE = new NavigationModeFeatureFlag(
+            ENABLE_QUICKSTEP_LIVE_TILE::get, mode -> mode.hasGestures);
+
+    private final Supplier<Boolean> mBasePredicate;
+    private final Predicate<SysUINavigationMode.Mode> mModePredicate;
+    private boolean mSupported;
+
+    private NavigationModeFeatureFlag(Supplier<Boolean> basePredicate,
+            Predicate<SysUINavigationMode.Mode> modePredicate) {
+        mBasePredicate = basePredicate;
+        mModePredicate = modePredicate;
+    }
+
+    public boolean get() {
+        return mBasePredicate.get() && mSupported;
+    }
+
+    public void initialize(Context context) {
+        onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(context).getMode());
+        SysUINavigationMode.INSTANCE.get(context).addModeChangeListener(this);
+    }
+
+    @Override
+    public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
+        mSupported = mModePredicate.test(newMode);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 8c98480..9537247 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -16,9 +16,9 @@
 package com.android.quickstep.util;
 
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
 import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation;
 import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
@@ -97,6 +97,7 @@
     private final FullscreenDrawParams mCurrentFullscreenParams;
     public final AnimatedFloat taskPrimaryTranslation = new AnimatedFloat();
     public final AnimatedFloat taskSecondaryTranslation = new AnimatedFloat();
+    public final AnimatedFloat gridTranslationSecondary = new AnimatedFloat();
 
     // RecentsView properties
     public final AnimatedFloat recentsViewScale = new AnimatedFloat();
@@ -316,9 +317,10 @@
         mMatrix.postScale(scale, scale, mIsRecentsRtl ? 0 : taskWidth, 0);
         float taskWidthDiff = taskWidth * (1 - gridScale);
         float taskWidthOffset = mIsRecentsRtl ? taskWidthDiff : -taskWidthDiff;
-        float translationPrimary = Utilities.mapRange(interpolatedGridProgress, 0, taskWidthOffset);
         mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
-                translationPrimary);
+                Utilities.mapRange(interpolatedGridProgress, 0, taskWidthOffset));
+        mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
+                Utilities.mapRange(interpolatedGridProgress, 0, gridTranslationSecondary.value));
 
         // Apply TaskView matrix: translate, scroll
         mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
@@ -352,7 +354,7 @@
                 .withCornerRadius(getCurrentCornerRadius())
                 .withShadowRadius(app.isTranslucent ? 0 : params.getShadowRadius());
 
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) {
+        if (LIVE_TILE.get() && params.getRecentsSurface() != null) {
             // When relativeLayer = 0, it reverts the surfaces back to the original order.
             builder.withRelativeLayerTo(params.getRecentsSurface(),
                     mDrawsBelowRecents ? Integer.MIN_VALUE : 0);
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index 9af4d30..e7101cc 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -130,16 +130,16 @@
         applyPrimaryTranslation();
     }
 
-    public float getScrollAdjustment() {
+    public float getScrollAdjustment(boolean gridEnabled) {
         float scrollAdjustment = 0;
-        if (mGridProgress > 0) {
+        if (gridEnabled) {
             scrollAdjustment += mGridTranslationPrimary;
         }
         return scrollAdjustment;
     }
 
-    public float getOffsetAdjustment() {
-        return getScrollAdjustment();
+    public float getOffsetAdjustment(boolean gridEnabled) {
+        return getScrollAdjustment(gridEnabled);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 84218fd..c62f3e2 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -23,7 +23,7 @@
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
@@ -95,7 +95,7 @@
     public void startHome() {
         Runnable onReachedHome = () -> mActivity.getStateManager().goToState(NORMAL, false);
         OverviewToHomeAnim overviewToHomeAnim = new OverviewToHomeAnim(mActivity, onReachedHome);
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+        if (LIVE_TILE.get()) {
             switchToScreenshot(null,
                     () -> finishRecentsAnimation(true /* toRecents */,
                             () -> overviewToHomeAnim.animateWithVelocity(0)));
@@ -160,6 +160,8 @@
             reset();
         }
         setOverlayEnabled(finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK);
+        setOverviewGridEnabled(finalState.displayOverviewTasksAsGrid(mActivity));
+        setOverviewFullscreenEnabled(finalState.getOverviewFullscreenProgress() == 1);
         setFreezeViewVisibility(false);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
deleted file mode 100644
index 8210ab0..0000000
--- a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
+++ /dev/null
@@ -1,190 +0,0 @@
-package com.android.quickstep.views;
-
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
-
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.util.FloatProperty;
-import android.view.ViewOverlay;
-
-import com.android.launcher3.anim.Interpolators;
-import com.android.quickstep.util.RecentsOrientedState.SurfaceRotation;
-
-public class LiveTileOverlay extends Drawable {
-
-    private static final long ICON_ANIM_DURATION = 120;
-
-    private static final FloatProperty<LiveTileOverlay> PROGRESS =
-            new FloatProperty<LiveTileOverlay>("progress") {
-                @Override
-                public void setValue(LiveTileOverlay liveTileOverlay, float progress) {
-                    liveTileOverlay.setIconAnimationProgress(progress);
-                }
-
-                @Override
-                public Float get(LiveTileOverlay liveTileOverlay) {
-                    return liveTileOverlay.mIconAnimationProgress;
-                }
-            };
-
-    public static final LiveTileOverlay INSTANCE = new LiveTileOverlay();
-
-    private final Paint mPaint = new Paint();
-    private final RectF mCurrentRect = new RectF();
-    private final Rect mBoundsRect = new Rect();
-
-    private @SurfaceRotation int mRotation = ROTATION_0;
-
-    private float mCornerRadius;
-    private Drawable mIcon;
-    private Animator mIconAnimator;
-
-    private float mIconAnimationProgress = 0f;
-    private boolean mIsAttached;
-
-    private LiveTileOverlay() {
-        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
-    }
-
-    public void update(RectF currentRect, float cornerRadius) {
-        invalidateSelf();
-
-        mCurrentRect.set(currentRect);
-        mCornerRadius = cornerRadius;
-
-        mCurrentRect.roundOut(mBoundsRect);
-        setBounds(mBoundsRect);
-        invalidateSelf();
-    }
-
-    public void update(float left, float top, float right, float bottom) {
-        mCurrentRect.set(left, top, right, bottom);
-    }
-
-    public void setRotation(@SurfaceRotation int rotation) {
-        mRotation = rotation;
-    }
-
-    public void setIcon(Drawable icon) {
-        mIcon = icon;
-    }
-
-    // TODO: consider cleaning this up and drawing icon in another way. Previously we place app
-    // below launcher during the initial swipe up and render the icon in this live tile overlay.
-    // However, this resulted in a bunch of touch input issues caused by Launcher getting the input
-    // events during transition (to overview / to another app (quick switch). So now our new
-    // solution places app on top in live tile until it fully settles in Overview.
-    public void startIconAnimation() {
-        if (mIconAnimator != null) {
-            mIconAnimator.cancel();
-        }
-        // This animator must match the icon part of {@link TaskView#FOCUS_TRANSITION} animation.
-        mIconAnimator = ObjectAnimator.ofFloat(this, PROGRESS, 1);
-        mIconAnimator.setDuration(ICON_ANIM_DURATION).setInterpolator(LINEAR);
-        mIconAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mIconAnimator = null;
-            }
-        });
-        mIconAnimator.start();
-    }
-
-    public float cancelIconAnimation() {
-        if (mIconAnimator != null) {
-            mIconAnimator.cancel();
-        }
-        return mIconAnimationProgress;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
-        if (mIcon != null && mIconAnimationProgress > 0f) {
-            canvas.save();
-            float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f,
-                    1f).getInterpolation(mIconAnimationProgress);
-
-            int iconRadius = mIcon.getBounds().width() / 2;
-            float dx = 0;
-            float dy = 0;
-
-            switch (mRotation) {
-                case ROTATION_0:
-                    dx = mCurrentRect.centerX() - iconRadius * scale;
-                    dy = mCurrentRect.top - iconRadius * scale;
-                    break;
-                case ROTATION_90:
-                    dx = mCurrentRect.right - iconRadius * scale;
-                    dy = mCurrentRect.centerY() - iconRadius * scale;
-                    break;
-                case ROTATION_270:
-                    dx = mCurrentRect.left - iconRadius * scale;
-                    dy = mCurrentRect.centerY() - iconRadius * scale;
-                    break;
-                case ROTATION_180:
-                    dx = mCurrentRect.centerX() - iconRadius * scale;
-                    dy = mCurrentRect.bottom - iconRadius * scale;
-                    break;
-            }
-
-            int rotationDegrees = mRotation * 90;
-            if (mRotation == ROTATION_90 || mRotation == ROTATION_270) {
-                canvas.rotate(rotationDegrees, dx + iconRadius, dy + iconRadius);
-            }
-            canvas.translate(dx, dy);
-            canvas.scale(scale, scale);
-            mIcon.draw(canvas);
-            canvas.restore();
-        }
-    }
-
-    @Override
-    public void setAlpha(int i) { }
-
-    @Override
-    public void setColorFilter(ColorFilter colorFilter) { }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    public boolean attach(ViewOverlay overlay) {
-        if (overlay != null && !mIsAttached) {
-            overlay.add(this);
-            mIsAttached = true;
-            return true;
-        }
-
-        return false;
-    }
-
-    public void detach(ViewOverlay overlay) {
-        if (overlay != null) {
-            overlay.remove(this);
-            mIsAttached = false;
-        }
-    }
-
-    private void setIconAnimationProgress(float progress) {
-        mIconAnimationProgress = progress;
-        invalidateSelf();
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index e20540c..bdd0a36 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -35,7 +35,6 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
@@ -43,6 +42,7 @@
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
@@ -72,6 +72,7 @@
 import android.text.TextPaint;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
+import android.util.Log;
 import android.util.SparseBooleanArray;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -104,6 +105,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
 import com.android.launcher3.util.DynamicResource;
@@ -297,6 +299,8 @@
     protected boolean mDisallowScrollToClearAll;
     private boolean mOverlayEnabled;
     protected boolean mFreezeViewVisibility;
+    private boolean mOverviewGridEnabled;
+    private boolean mOverviewFullscreenEnabled;
 
     private float mAdjacentPageOffset = 0;
     private float mTaskViewsSecondaryTranslation = 0;
@@ -420,7 +424,6 @@
     private boolean mShowEmptyMessage;
     private OnEmptyMessageUpdatedListener mOnEmptyMessageUpdatedListener;
     private Layout mEmptyTextLayout;
-    private boolean mLiveTileOverlayAttached;
 
     // Keeps track of the index where the first TaskView should be
     private int mTaskViewStartIndex = 0;
@@ -561,7 +564,7 @@
     @Override
     protected void onWindowVisibilityChanged(int visibility) {
         super.onWindowVisibilityChanged(visibility);
-        if (visibility != VISIBLE && ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+        if (visibility != VISIBLE && LIVE_TILE.get()) {
             finishRecentsAnimation(true /* toRecents */, null);
         }
         updateTaskStackListenerState();
@@ -675,8 +678,10 @@
     }
 
     private boolean isTaskViewWithinBounds(TaskView tv, int start, int end) {
-        int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment();
-        int taskSize = (int) (mOrientationHandler.getMeasuredSize(tv) * tv.getSizeAdjustment());
+        int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment(
+                mOverviewFullscreenEnabled, mOverviewGridEnabled);
+        int taskSize = (int) (mOrientationHandler.getMeasuredSize(tv) * tv.getSizeAdjustment(
+                mOverviewFullscreenEnabled, mOverviewGridEnabled));
         int taskEnd = taskStart + taskSize;
         return (taskStart >= start && taskStart <= end) || (taskEnd >= start
                 && taskEnd <= end);
@@ -823,6 +828,12 @@
     }
 
     protected void applyLoadPlan(ArrayList<Task> tasks) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.GET_RECENTS_FAILED, "applyLoadPlan: taskCount=" + tasks.size());
+            for (Task t : tasks) {
+                Log.d(TestProtocol.GET_RECENTS_FAILED, "\t" + t);
+            }
+        }
         if (mPendingAnimation != null) {
             mPendingAnimation.addEndListener(success -> applyLoadPlan(tasks));
             return;
@@ -884,12 +895,21 @@
         resetTaskVisuals();
         onTaskStackUpdated();
         updateEnabledOverlays();
+
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.GET_RECENTS_FAILED, "applyLoadPlan: taskViewCount="
+                    + getTaskViewCount());
+        }
     }
 
     private boolean isModal() {
         return mTaskModalness > 0;
     }
 
+    public boolean isLoadingTasks() {
+        return mModel.isLoadingTasksInBackground();
+    }
+
     private void removeTasksViewsAndClearAllButton() {
         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
             removeView(getTaskViewAt(i));
@@ -900,6 +920,12 @@
     }
 
     public int getTaskViewCount() {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.GET_RECENTS_FAILED, "getTaskViewCount:"
+                    + " numChildren=" + getChildCount()
+                    + " start=" + mTaskViewStartIndex
+                    + " clearAll=" + indexOfChild(mClearAllButton));
+        }
         int taskViewCount = getChildCount() - mTaskViewStartIndex;
         if (indexOfChild(mClearAllButton) != -1) {
             taskViewCount--;
@@ -922,16 +948,14 @@
                 taskView.setModalness(mTaskModalness);
             }
         }
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+        if (LIVE_TILE.get()) {
             // Since we reuse the same mLiveTileTaskViewSimulator in the RecentsView, we need
             // to reset the params after it settles in Overview from swipe up so that we don't
             // render with obsolete param values.
+            mLiveTileTaskViewSimulator.taskPrimaryTranslation.value = 0;
+            mLiveTileTaskViewSimulator.taskSecondaryTranslation.value = 0;
             mLiveTileTaskViewSimulator.fullScreenProgress.value = 0;
             mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
-
-            // Reset the live tile rect
-            DeviceProfile deviceProfile = mActivity.getDeviceProfile();
-            LiveTileOverlay.INSTANCE.update(0, 0, deviceProfile.widthPx, deviceProfile.heightPx);
         }
         if (mRunningTaskTileHidden) {
             setRunningTaskHidden(mRunningTaskTileHidden);
@@ -1042,7 +1066,7 @@
         mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast);
 
         mLiveTileTaskViewSimulator.setScroll(getScrollOffset());
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
+        if (LIVE_TILE.get() && mEnableDrawingLiveTile
                 && mLiveTileParams.getTargetSet() != null) {
             redrawLiveTile();
         }
@@ -1264,10 +1288,7 @@
      */
     public void onSwipeUpAnimationSuccess() {
         if (getRunningTaskView() != null) {
-            float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlayAttached
-                    ? LiveTileOverlay.INSTANCE.cancelIconAnimation()
-                    : 0f;
-            animateUpRunningTaskIconScale(startProgress);
+            animateUpRunningTaskIconScale(0f);
         }
         setSwipeDownShouldLaunchApp(true);
     }
@@ -1326,7 +1347,7 @@
         setOnScrollChangeListener(null);
         setEnableFreeScroll(true);
         setEnableDrawingLiveTile(true);
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+        if (!LIVE_TILE.get()) {
             setRunningTaskViewShowScreenshot(true);
         }
         setRunningTaskHidden(false);
@@ -1418,7 +1439,7 @@
     }
 
     private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+        if (LIVE_TILE.get()) {
             TaskView runningTaskView = getRunningTaskView();
             if (runningTaskView != null) {
                 runningTaskView.setShowScreenshot(showScreenshot);
@@ -1824,7 +1845,7 @@
             anim.addOnFrameCallback(this::updateCurveProperties);
         }
 
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && getRunningTaskView() == taskView) {
+        if (LIVE_TILE.get() && getRunningTaskView() == taskView) {
             anim.addOnFrameCallback(() -> {
                 mLiveTileTaskViewSimulator.taskSecondaryTranslation.value =
                         mOrientationHandler.getSecondaryValue(
@@ -1843,7 +1864,7 @@
         mPendingAnimation.addEndListener(new Consumer<Boolean>() {
             @Override
             public void accept(Boolean success) {
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask() && success) {
+                if (LIVE_TILE.get() && taskView.isRunningTask() && success) {
                     finishRecentsAnimation(true /* toHome */, () -> onEnd(success));
                 } else {
                     onEnd(success);
@@ -2390,7 +2411,7 @@
             anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex),
                     mOrientationHandler.getPrimaryViewTranslate(), primaryTranslation));
             int runningTaskIndex = recentsView.getRunningTaskIndex();
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get() && runningTaskIndex != -1
+            if (LIVE_TILE.get() && runningTaskIndex != -1
                     && runningTaskIndex != taskIndex) {
                 anim.play(ObjectAnimator.ofFloat(
                         recentsView.getLiveTileTaskViewSimulator().taskPrimaryTranslation,
@@ -2467,7 +2488,7 @@
 
         mPendingAnimation = new PendingAnimation(duration);
         mPendingAnimation.add(anim);
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+        if (LIVE_TILE.get()) {
             mLiveTileTaskViewSimulator.addOverviewToAppAnim(mPendingAnimation, interpolator);
             mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
         }
@@ -2479,7 +2500,7 @@
                         tv.notifyTaskLaunchFailed(TAG);
                     }
                 };
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+                if (LIVE_TILE.get()) {
                     finishRecentsAnimation(false /* toRecents */, null);
                     onLaunchResult.accept(true /* success */);
                 } else {
@@ -2589,16 +2610,6 @@
         }
     }
 
-    public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) {
-        mLiveTileOverlayAttached = liveTileOverlayAttached;
-    }
-
-    public void updateLiveTileIcon(Drawable icon) {
-        if (mLiveTileOverlayAttached) {
-            LiveTileOverlay.INSTANCE.setIcon(icon);
-        }
-    }
-
     public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
         if (mRecentsAnimationController == null) {
             if (onFinishComplete != null) {
@@ -2657,9 +2668,10 @@
             View child = getChildAt(i);
             float scrollDiff = 0;
             if (child instanceof TaskView) {
-                scrollDiff = ((TaskView) child).getScrollAdjustment();
+                scrollDiff = ((TaskView) child).getScrollAdjustment(mOverviewFullscreenEnabled,
+                        mOverviewGridEnabled);
             } else if (child instanceof ClearAllButton) {
-                scrollDiff = ((ClearAllButton) child).getScrollAdjustment();
+                scrollDiff = ((ClearAllButton) child).getScrollAdjustment(mOverviewGridEnabled);
             }
 
             if (scrollDiff != 0) {
@@ -2675,9 +2687,10 @@
         int childOffset = super.getChildOffset(index);
         View child = getChildAt(index);
         if (child instanceof TaskView) {
-            childOffset += ((TaskView) child).getOffsetAdjustment();
+            childOffset += ((TaskView) child).getOffsetAdjustment(mOverviewFullscreenEnabled,
+                    mOverviewGridEnabled);
         } else if (child instanceof ClearAllButton) {
-            childOffset += ((ClearAllButton) child).getOffsetAdjustment();
+            childOffset += ((ClearAllButton) child).getOffsetAdjustment(mOverviewGridEnabled);
         }
         return childOffset;
     }
@@ -2688,7 +2701,8 @@
         if (taskView == null) {
             return super.getChildVisibleSize(index);
         }
-        return (int) (super.getChildVisibleSize(index) * taskView.getSizeAdjustment());
+        return (int) (super.getChildVisibleSize(index) * taskView.getSizeAdjustment(
+                mOverviewFullscreenEnabled, mOverviewGridEnabled));
     }
 
     @Override
@@ -2729,7 +2743,7 @@
     }
 
     /**
-     * @return How many pixels the page is offset on the currently laid out dominant axis.
+     * Returns how many pixels the page is offset on the currently laid out dominant axis.
      */
     public int getScrollOffset(int pageIndex) {
         if (pageIndex == -1) {
@@ -2745,6 +2759,20 @@
         return getScrollForPage(pageIndex) - scroll;
     }
 
+    /**
+     * Returns how many pixels the task is offset on the currently laid out secondary axis
+     * according to {@link #mGridProgress}.
+     */
+    public float getGridTranslationSecondary(int pageIndex) {
+        TaskView taskView = getTaskViewAtByAbsoluteIndex(pageIndex);
+        if (taskView == null) {
+            return 0;
+        }
+
+        return mOrientationHandler.getSecondaryValue(taskView.getGridTranslationX(),
+                taskView.getGridTranslationY());
+    }
+
     public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
         float degreesRotated;
         if (navbarRotation == 0) {
@@ -2789,6 +2817,23 @@
         }
     }
 
+    public void setOverviewGridEnabled(boolean overviewGridEnabled) {
+        if (mOverviewGridEnabled != overviewGridEnabled) {
+            mOverviewGridEnabled = overviewGridEnabled;
+            // Request layout to ensure scroll position is recalculated with updated mGridProgress.
+            requestLayout();
+        }
+    }
+
+    public void setOverviewFullscreenEnabled(boolean overviewFullscreenEnabled) {
+        if (mOverviewFullscreenEnabled != overviewFullscreenEnabled) {
+            mOverviewFullscreenEnabled = overviewFullscreenEnabled;
+            // Request layout to ensure scroll position is recalculated with updated
+            // mFullscreenProgress.
+            requestLayout();
+        }
+    }
+
     /**
      * Switch the current running task view to static snapshot mode,
      * capturing the snapshot at the same time.
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index f477ca7..2315147 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,7 +16,7 @@
 
 package com.android.quickstep.views;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
 
 import android.animation.Animator;
@@ -183,7 +183,7 @@
         LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
         mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp);
         menuOptionView.setOnClickListener(view -> {
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (LIVE_TILE.get()) {
                 RecentsView recentsView = mTaskView.getRecentsView();
                 recentsView.switchToScreenshot(null,
                         () -> recentsView.finishRecentsAnimation(true /* toRecents */,
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index f2f4bc1..4c21745 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -19,7 +19,7 @@
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
 
 import android.content.Context;
@@ -319,7 +319,7 @@
 
     public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
             float cornerRadius) {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+        if (LIVE_TILE.get()) {
             if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
                 canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint);
                 canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 20a8444..88545c6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -35,10 +35,10 @@
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -308,7 +308,7 @@
             if (getTask() == null) {
                 return;
             }
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
+            if (LIVE_TILE.get() && isRunningTask()) {
                 if (!mIsClickableAsLiveTile) {
                     return;
                 }
@@ -559,9 +559,6 @@
             mIconLoadRequest = iconCache.updateIconInBackground(mTask,
                     (task) -> {
                         setIcon(task.icon);
-                        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
-                            getRecentsView().updateLiveTileIcon(task.icon);
-                        }
                         mDigitalWellBeingToast.initialize(mTask);
                     });
         } else {
@@ -919,11 +916,19 @@
         applyTranslationX();
     }
 
+    public float getGridTranslationX() {
+        return mGridTranslationX;
+    }
+
     public void setGridTranslationY(float gridTranslationY) {
         mGridTranslationY = gridTranslationY;
         applyTranslationY();
     }
 
+    public float getGridTranslationY() {
+        return mGridTranslationY;
+    }
+
     public void setGridOffsetTranslationX(float gridOffsetTranslationX) {
         mGridOffsetTranslationX = gridOffsetTranslationX;
         applyTranslationX();
@@ -933,31 +938,31 @@
         mNonRtlVisibleOffset = nonRtlVisibleOffset;
     }
 
-    public float getScrollAdjustment() {
+    public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
         float scrollAdjustment = 0;
-        if (mFullscreenProgress > 0) {
+        if (fullscreenEnabled) {
             scrollAdjustment += mFullscreenTranslationX + mAccumulatedFullscreenTranslationX;
         }
-        if (mGridProgress > 0) {
+        if (gridEnabled) {
             scrollAdjustment += mGridTranslationX;
         }
         return scrollAdjustment;
     }
 
-    public float getOffsetAdjustment() {
-        float offsetAdjustment = getScrollAdjustment();
-        if (mGridProgress > 0) {
+    public float getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
+        float offsetAdjustment = getScrollAdjustment(fullscreenEnabled, gridEnabled);
+        if (gridEnabled) {
             offsetAdjustment += mGridOffsetTranslationX + mNonRtlVisibleOffset;
         }
         return offsetAdjustment;
     }
 
-    public float getSizeAdjustment() {
+    public float getSizeAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
         float sizeAdjustment = 1;
-        if (mFullscreenProgress > 0) {
+        if (fullscreenEnabled) {
             sizeAdjustment *= mFullscreenScale;
         }
-        if (mGridProgress > 0) {
+        if (gridEnabled) {
             sizeAdjustment *= mGridScale;
         }
         return sizeAdjustment;
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 2e7e6e0..713fd07 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -42,6 +42,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.os.RemoteException;
+import android.util.Log;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -55,6 +56,7 @@
 import com.android.launcher3.tapl.OverviewTask;
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.quickstep.views.RecentsView;
@@ -172,9 +174,15 @@
 
     protected <T> T getFromRecents(Function<RecentsActivity, T> f) {
         if (!TestHelpers.isInLauncherProcess()) return null;
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.GET_RECENTS_FAILED, "getFromRecents");
+        }
         Object[] result = new Object[1];
         Wait.atMost("Failed to get from recents", () -> MAIN_EXECUTOR.submit(() -> {
             RecentsActivity activity = RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity();
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.GET_RECENTS_FAILED, "activity=" + activity);
+            }
             if (activity == null) {
                 return false;
             }
@@ -200,8 +208,13 @@
                 () -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
 
         BaseOverview overview = mLauncher.getBackground().switchToOverview();
-        executeOnRecents(recents ->
-                assertTrue("Don't have at least 3 tasks", getTaskCount(recents) >= 3));
+        executeOnRecents(recents -> {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.GET_RECENTS_FAILED, "isLoading=" +
+                        recents.<RecentsView>getOverviewPanel().isLoadingTasks());
+            }
+            assertTrue("Don't have at least 3 tasks", getTaskCount(recents) >= 3);
+        });
 
         // Test flinging forward and backward.
         overview.flingForward();
diff --git a/res/drawable-hdpi/widget_resize_frame.9.png b/res/drawable-hdpi/widget_resize_frame.9.png
deleted file mode 100644
index a710932..0000000
--- a/res/drawable-hdpi/widget_resize_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/widget_resize_shadow.9.png b/res/drawable-hdpi/widget_resize_shadow.9.png
deleted file mode 100644
index 7cb5214..0000000
--- a/res/drawable-hdpi/widget_resize_shadow.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_frame.9.png b/res/drawable-mdpi/widget_resize_frame.9.png
deleted file mode 100644
index 252482f..0000000
--- a/res/drawable-mdpi/widget_resize_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_shadow.9.png b/res/drawable-mdpi/widget_resize_shadow.9.png
deleted file mode 100644
index a2010e2..0000000
--- a/res/drawable-mdpi/widget_resize_shadow.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_frame.9.png b/res/drawable-xhdpi/widget_resize_frame.9.png
deleted file mode 100644
index 563c75d..0000000
--- a/res/drawable-xhdpi/widget_resize_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_shadow.9.png b/res/drawable-xhdpi/widget_resize_shadow.9.png
deleted file mode 100644
index 2b1ac05..0000000
--- a/res/drawable-xhdpi/widget_resize_shadow.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_frame.9.png b/res/drawable-xxhdpi/widget_resize_frame.9.png
deleted file mode 100644
index ea527f4..0000000
--- a/res/drawable-xxhdpi/widget_resize_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_shadow.9.png b/res/drawable-xxhdpi/widget_resize_shadow.9.png
deleted file mode 100644
index 5412168..0000000
--- a/res/drawable-xxhdpi/widget_resize_shadow.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/widget_resize_frame.9.png b/res/drawable-xxxhdpi/widget_resize_frame.9.png
deleted file mode 100644
index 4644e9a..0000000
--- a/res/drawable-xxxhdpi/widget_resize_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/widget_resize_shadow.9.png b/res/drawable-xxxhdpi/widget_resize_shadow.9.png
deleted file mode 100644
index 63cea84..0000000
--- a/res/drawable-xxxhdpi/widget_resize_shadow.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/widget_resize_frame.xml b/res/drawable/widget_resize_frame.xml
new file mode 100644
index 0000000..d157f5d
--- /dev/null
+++ b/res/drawable/widget_resize_frame.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2021 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@android:color/transparent" />
+    <corners android:radius="@android:dimen/system_app_widget_background_radius" />
+    <stroke android:width="2dp" android:color="?android:attr/colorAccent" />
+</shape>
\ No newline at end of file
diff --git a/res/layout/add_item_confirmation_activity.xml b/res/layout/add_item_confirmation_activity.xml
index 830255b..b1a1efe 100644
--- a/res/layout/add_item_confirmation_activity.xml
+++ b/res/layout/add_item_confirmation_activity.xml
@@ -46,7 +46,7 @@
                 android:background="?android:attr/colorPrimaryDark"
                 android:theme="?attr/widgetsTheme">
 
-                <com.android.launcher3.dragndrop.LivePreviewWidgetCell
+                <com.android.launcher3.widget.WidgetCell
                     android:id="@+id/widget_cell"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
@@ -59,7 +59,7 @@
 
                     <include layout="@layout/widget_cell_content"  />
 
-                </com.android.launcher3.dragndrop.LivePreviewWidgetCell>
+                </com.android.launcher3.widget.WidgetCell>
             </FrameLayout>
         </LinearLayout>
     </ScrollView>
diff --git a/res/layout/app_widget_resize_frame.xml b/res/layout/app_widget_resize_frame.xml
index 12561b6..dfce946 100644
--- a/res/layout/app_widget_resize_frame.xml
+++ b/res/layout/app_widget_resize_frame.xml
@@ -18,15 +18,20 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@drawable/widget_resize_shadow"
-    android:foreground="@drawable/widget_resize_frame"
-    android:foregroundTint="?attr/workspaceTextColor"
     android:padding="0dp">
 
     <FrameLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent" >
 
+        <!-- Frame -->
+        <ImageView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:layout_margin="@dimen/resize_frame_margin"
+            android:src="@drawable/widget_resize_frame" />
+
         <!-- Left -->
         <ImageView
             android:layout_width="wrap_content"
@@ -34,7 +39,7 @@
             android:layout_gravity="left|center_vertical"
             android:layout_marginLeft="@dimen/widget_handle_margin"
             android:src="@drawable/ic_widget_resize_handle"
-            android:tint="?attr/workspaceTextColor" />
+            android:tint="?android:attr/colorAccent" />
 
         <!-- Top -->
         <ImageView
@@ -43,7 +48,7 @@
             android:layout_gravity="top|center_horizontal"
             android:layout_marginTop="@dimen/widget_handle_margin"
             android:src="@drawable/ic_widget_resize_handle"
-            android:tint="?attr/workspaceTextColor" />
+            android:tint="?android:attr/colorAccent" />
 
         <!-- Right -->
         <ImageView
@@ -52,7 +57,7 @@
             android:layout_gravity="right|center_vertical"
             android:layout_marginRight="@dimen/widget_handle_margin"
             android:src="@drawable/ic_widget_resize_handle"
-            android:tint="?attr/workspaceTextColor" />
+            android:tint="?android:attr/colorAccent" />
 
         <!-- Bottom -->
         <ImageView
@@ -61,7 +66,7 @@
             android:layout_gravity="bottom|center_horizontal"
             android:layout_marginBottom="@dimen/widget_handle_margin"
             android:src="@drawable/ic_widget_resize_handle"
-            android:tint="?attr/workspaceTextColor" />
+            android:tint="?android:attr/colorAccent" />
 
     </FrameLayout>
 </com.android.launcher3.AppWidgetResizeFrame>
\ No newline at end of file
diff --git a/res/layout/live_preview_widget_cell.xml b/res/layout/live_preview_widget_cell.xml
deleted file mode 100644
index 1e1ce6e..0000000
--- a/res/layout/live_preview_widget_cell.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<com.android.launcher3.dragndrop.LivePreviewWidgetCell
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="0dp"
-    android:layout_height="wrap_content"
-    android:paddingHorizontal="@dimen/widget_cell_horizontal_padding"
-    android:paddingVertical="@dimen/widget_cell_vertical_padding"
-    android:layout_weight="1"
-    android:orientation="vertical"
-    android:focusable="true"
-    android:background="?android:attr/colorPrimaryDark"
-    android:gravity="center_horizontal">
-
-    <include layout="@layout/widget_cell_content" />
-
-</com.android.launcher3.dragndrop.LivePreviewWidgetCell>
\ No newline at end of file
diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml
index 73a5737..55dd1de 100644
--- a/res/layout/widget_cell.xml
+++ b/res/layout/widget_cell.xml
@@ -22,7 +22,6 @@
     android:layout_weight="1"
     android:orientation="vertical"
     android:focusable="true"
-    android:background="?android:attr/colorPrimaryDark"
     android:gravity="center_horizontal">
 
     <include layout="@layout/widget_cell_content"  />
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index 28a8c6f..6c18d7a 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -15,6 +15,7 @@
 -->
 <com.android.launcher3.widget.picker.WidgetsFullSheet
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
@@ -27,6 +28,14 @@
         android:background="?android:attr/colorPrimary"
         android:elevation="4dp">
 
+        <TextView
+            android:id="@+id/no_widgets_text"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center"
+            android:visibility="gone"
+            tools:text="No widgets available" />
+
         <!-- Fast scroller popup -->
         <TextView
             android:id="@+id/fast_scroller_popup"
diff --git a/res/layout/widgets_list_row_header.xml b/res/layout/widgets_list_row_header.xml
index faff10c..041e007 100644
--- a/res/layout/widgets_list_row_header.xml
+++ b/res/layout/widgets_list_row_header.xml
@@ -52,7 +52,7 @@
             android:id="@+id/app_subtitle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            tools:text="n widgets" />
+            tools:text="m widgets, n shortcuts" />
 
     </LinearLayout>
 
diff --git a/res/layout/widgets_scroll_container.xml b/res/layout/widgets_scroll_container.xml
deleted file mode 100644
index fc509d1..0000000
--- a/res/layout/widgets_scroll_container.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 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.
--->
-
-<HorizontalScrollView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/widgets_scroll_container"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:background="?android:attr/colorPrimaryDark"
-    android:scrollbars="none">
-    <LinearLayout
-        android:id="@+id/widgets_cell_list"
-        style="@style/TextTitle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingStart="0dp"
-        android:paddingEnd="0dp"
-        android:orientation="horizontal"
-        android:showDividers="none"/>
-</HorizontalScrollView>
\ No newline at end of file
diff --git a/res/values-sw720dp/config.xml b/res/values-sw720dp/config.xml
index 1f401c4..ec07591 100644
--- a/res/values-sw720dp/config.xml
+++ b/res/values-sw720dp/config.xml
@@ -1,5 +1,4 @@
 <resources>
-    <bool name="config_largeHeap">true</bool>
 
 <!-- All Apps & Widgets -->
     <!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 3b171c6..da43758 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -52,6 +52,7 @@
 <!-- App Widget resize frame -->
     <dimen name="widget_handle_margin">13dp</dimen>
     <dimen name="resize_frame_background_padding">24dp</dimen>
+    <dimen name="resize_frame_margin">22dp</dimen>
 
 <!-- Fast scroll -->
     <dimen name="fastscroll_track_min_width">6dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 73f9e53..6fb72b1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -52,17 +52,31 @@
     <string name="add_item_request_drag_hint">Touch &amp; hold to place manually</string>
     <!-- Button label to automatically add icon on home screen [CHAR_LIMIT=50] -->
     <string name="place_automatically">Add automatically</string>
-    <!-- Label for showing the number of widgets an app has in the full widgets picker. [CHAR_LIMIT=25] -->
-    <plurals name="widgets_tray_subtitle">
-        <item quantity="one"><xliff:g id="widget_count" example="1">%1$d</xliff:g> widget</item>
-        <item quantity="other"><xliff:g id="widget_count" example="2">%1$d</xliff:g> widgets</item>
+    <!-- Label for showing the number of widgets an app has in the full widgets picker.
+         [CHAR_LIMIT=25] -->
+    <plurals name="widgets_count">
+        <item quantity="one"><xliff:g id="widgets_count" example="1">%1$d</xliff:g> widget</item>
+        <item quantity="other"><xliff:g id="widgets_count" example="2">%1$d</xliff:g> widgets</item>
     </plurals>
+    <!-- Label for showing the number of shortcut an app has in the full widgets picker.
+         [CHAR_LIMIT=25] -->
+    <plurals name="shortcuts_count">
+        <item quantity="one"><xliff:g id="shortcuts_count" example="1">%1$d</xliff:g> shortcut</item>
+        <item quantity="other"><xliff:g id="shortcuts_count" example="2">%1$d</xliff:g> shortcuts</item>
+    </plurals>
+    <!-- Label for showing both the number of widgets and shortcuts an app has in the full widgets
+         picker. [CHAR_LIMIT=50] -->
+    <string name="widgets_and_shortcuts_count"><xliff:g id="widgets_count" example="5 widgets">%1$s</xliff:g>, <xliff:g id="shortcuts_count" example="1 shortcut">%2$s</xliff:g></string>
+
     <!-- Text for both the tile of a popup view, which shows all available widgets installed on
          the device, and the text of a button, which opens this popup view. [CHAR LIMIT=30]-->
     <string name="widget_button_text">Widgets</string>
     <!-- Search bar text shown in the popup view showing all available widgets installed on the
          device. [CHAR_LIMIT=50] -->
     <string name="widgets_full_sheet_search_bar_hint">Search</string>
+    <!-- Text shown when there is no widgets shown in the popup view showing all available widgets
+         installed on the device. [CHAR_LIMIT=none] -->
+    <string name="no_widgets_available">No widgets available</string>
 
     <!-- All Apps -->
     <!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
diff --git a/robolectric_tests/Android.bp b/robolectric_tests/Android.bp
new file mode 100644
index 0000000..c738df9
--- /dev/null
+++ b/robolectric_tests/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 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.
+
+//
+// Launcher Robolectric test target.
+//
+//        "robolectric_android-all-stub", not needed, we write our own stubs
+android_robolectric_test {
+    name: "LauncherRoboTests",
+    srcs: [
+        "src/**/*.java",
+    ],
+    java_resource_dirs: [
+        "resources",
+        "res",
+        "config",
+    ],
+    static_libs: [
+        "truth-prebuilt",
+        "Launcher3TestCommon",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "mockito-robolectric-prebuilt",
+    ],
+    //robolectric_prebuilt_version: "4.4",
+    libs: [
+        "platform-robolectric-4.4-prebuilt",
+    ],
+    instrumentation_for: "Launcher3",
+
+    test_options: {
+        timeout: 36000,
+    },
+}
+
diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
deleted file mode 100644
index 405a458..0000000
--- a/robolectric_tests/Android.mk
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#############################################
-# Launcher Robolectric test target.         #
-#############################################
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := LauncherRoboTests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-
-LOCAL_SDK_VERSION := system_current
-LOCAL_SRC_FILES := \
-	$(call all-java-files-under, src) \
-	$(call all-java-files-under, ../tests/src_common)
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    androidx.test.runner \
-    androidx.test.rules \
-    mockito-robolectric-prebuilt \
-    truth-prebuilt
-LOCAL_JAVA_LIBRARIES := \
-    platform-robolectric-4.3.1-prebuilt
-
-LOCAL_JAVA_RESOURCE_DIRS := resources config
-
-LOCAL_INSTRUMENTATION_FOR := Launcher3
-LOCAL_MODULE_TAGS := optional
-
-# Generate test_config.properties
-include external/robolectric-shadows/gen_test_config.mk
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-############################################
-# Target to run the previous target.       #
-############################################
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := RunLauncherRoboTests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
-LOCAL_SDK_VERSION := system_current
-LOCAL_JAVA_LIBRARIES := LauncherRoboTests
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_TEST_PACKAGE := Launcher3
-LOCAL_INSTRUMENT_SOURCE_DIRS := packages/apps/Launcher3/src
-
-LOCAL_ROBOTEST_TIMEOUT := 36000
-
-include prebuilts/misc/common/robolectric/4.4/run_robotests.mk
diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/config/robolectric.properties
index 936ce12..1b170e1 100644
--- a/robolectric_tests/config/robolectric.properties
+++ b/robolectric_tests/config/robolectric.properties
@@ -1,4 +1,5 @@
 sdk=29
+
 shadows= \
     com.android.launcher3.shadows.LShadowAppPredictionManager \
     com.android.launcher3.shadows.LShadowAppWidgetManager \
diff --git a/robolectric_tests/resources/widgets_predication_update_task_data.txt b/robolectric_tests/resources/widgets_predication_update_task_data.txt
new file mode 100644
index 0000000..941d195
--- /dev/null
+++ b/robolectric_tests/resources/widgets_predication_update_task_data.txt
@@ -0,0 +1,24 @@
+# Model data used by WidgetsPredictionUpdateTasksTest
+
+classMap s com.android.launcher3.model.data.WorkspaceItemInfo
+classMap w com.android.launcher3.model.data.LauncherAppWidgetInfo
+
+# Items for the BgDataModel
+
+# App shortcuts
+bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
+bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
+bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
+bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
+
+# Promise icons for app3
+bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
+bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
+bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=7
+
+# Promise icon for app4
+bgItem s itemType=1 status=1 title=app4-shrt intent=component=app4/class1 id=8
+
+# Widget
+bgItem w providerName=app4/provider1 id=9
+bgItem w providerName=app5/provider1 id=10
\ No newline at end of file
diff --git a/robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java b/robolectric_tests/unstaged/SettingsCacheTest.java
similarity index 100%
rename from robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java
rename to robolectric_tests/unstaged/SettingsCacheTest.java
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index a813275..9d6af9f 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -12,12 +12,11 @@
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.util.AttributeSet;
+import android.util.SizeF;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
@@ -27,7 +26,6 @@
 
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
 import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.util.FocusLogic;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -368,7 +366,7 @@
 
     public static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
                                               int spanX, int spanY) {
-        List<PointF> sizes = getWidgetSizes(launcher, spanX, spanY);
+        List<SizeF> sizes = getWidgetSizes(launcher, spanX, spanY);
         if (ATLEAST_S) {
             widgetView.updateAppWidgetSize(new Bundle(), sizes);
         } else {
@@ -378,43 +376,25 @@
         }
     }
 
-    private static PointF getWidgetSize(Context context, Point cellSize, int spanX, int spanY,
+    private static SizeF getWidgetSize(Context context, Point cellSize, int spanX, int spanY,
             int borderSpacing) {
         final float density = context.getResources().getDisplayMetrics().density;
         final float hBorderSpacing = (spanX - 1) * borderSpacing;
         final float vBorderSpacing = (spanY - 1) * borderSpacing;
 
-        PointF widgetSize = new PointF();
-        widgetSize.x = ((spanX * cellSize.x) + hBorderSpacing) / density;
-        widgetSize.y = ((spanY * cellSize.y) + vBorderSpacing) / density;
-        return widgetSize;
-    }
-
-    /** Returns the actual widget size given its span. */
-    public static PointF getWidgetSize(Context context, int spanX, int spanY) {
-        final Point[] cellSize = CELL_SIZE.get(context);
-        final int[] borderSpacing = BORDER_SPACING_SIZE.get(context);
-        if (isLandscape(context)) {
-            return getWidgetSize(context, cellSize[0], spanX, spanY, borderSpacing[0]);
-        }
-        return getWidgetSize(context, cellSize[1], spanX, spanY, borderSpacing[1]);
-    }
-
-    /** Returns true if the screen is in landscape mode. */
-    private static boolean isLandscape(Context context) {
-        return context.getResources().getConfiguration().orientation
-                == Configuration.ORIENTATION_LANDSCAPE;
+        return new SizeF(((spanX * cellSize.x) + hBorderSpacing) / density,
+                ((spanY * cellSize.y) + vBorderSpacing) / density);
     }
 
     /** Returns the list of sizes for a widget of given span, in dp. */
-    public static ArrayList<PointF> getWidgetSizes(Context context, int spanX, int spanY) {
+    public static ArrayList<SizeF> getWidgetSizes(Context context, int spanX, int spanY) {
         final Point[] cellSize = CELL_SIZE.get(context);
         final int[] borderSpacing = BORDER_SPACING_SIZE.get(context);
 
-        PointF landSize = getWidgetSize(context, cellSize[0], spanX, spanY, borderSpacing[0]);
-        PointF portSize = getWidgetSize(context, cellSize[1], spanX, spanY,  borderSpacing[1]);
+        SizeF landSize = getWidgetSize(context, cellSize[0], spanX, spanY, borderSpacing[0]);
+        SizeF portSize = getWidgetSize(context, cellSize[1], spanX, spanY, borderSpacing[1]);
 
-        ArrayList<PointF> sizes = new ArrayList<>(2);
+        ArrayList<SizeF> sizes = new ArrayList<>(2);
         sizes.add(landSize);
         sizes.add(portSize);
         return sizes;
@@ -430,17 +410,18 @@
      * the right (resp. bottom) for the max. The returned rectangle is set with 0s if the list is
      * empty.
      */
-    public static Rect getMinMaxSizes(List<PointF> sizes, @Nullable Rect outRect) {
+    public static Rect getMinMaxSizes(List<SizeF> sizes, @Nullable Rect outRect) {
         if (outRect == null) {
             outRect = new Rect();
         }
         if (sizes.isEmpty()) {
             outRect.set(0, 0, 0, 0);
         } else {
-            PointF first = sizes.get(0);
-            outRect.set((int) first.x, (int) first.y, (int) first.x, (int) first.y);
+            SizeF first = sizes.get(0);
+            outRect.set((int) first.getWidth(), (int) first.getHeight(), (int) first.getWidth(),
+                    (int) first.getHeight());
             for (int i = 1; i < sizes.size(); i++) {
-                outRect.union((int) sizes.get(i).x, (int) sizes.get(i).y);
+                outRect.union((int) sizes.get(i).getWidth(), (int) sizes.get(i).getHeight());
             }
         }
         return outRect;
@@ -562,7 +543,7 @@
     @Override
     public boolean onKey(View v, int keyCode, KeyEvent event) {
         // Clear the frame and give focus to the widget host view when a directional key is pressed.
-        if (FocusLogic.shouldConsume(keyCode)) {
+        if (shouldConsume(keyCode)) {
             close(false);
             mWidgetView.requestFocus();
             return true;
@@ -685,4 +666,14 @@
             return moveEnd ? out.size() - size() : size() - out.size();
         }
     }
+
+    /**
+     * Returns true only if this utility class handles the key code.
+     */
+    public static boolean shouldConsume(int keyCode) {
+        return (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
+                || keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN
+                || keyCode == KeyEvent.KEYCODE_MOVE_HOME || keyCode == KeyEvent.KEYCODE_MOVE_END
+                || keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN);
+    }
 }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index adafea2..2440854 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -641,7 +641,8 @@
                 padding.right = hotseatBarSizePx;
             }
         } else {
-            int paddingBottom = hotseatBarSizePx + workspacePageIndicatorHeight
+            int hotseatTop = isTaskbarPresent ? taskbarSize : hotseatBarSizePx;
+            int paddingBottom = hotseatTop + workspacePageIndicatorHeight
                     + workspaceBottomPadding - mWorkspacePageIndicatorOverlapWorkspace;
             if (isTablet) {
                 // Pad the left and right of the workspace to ensure consistent spacing
@@ -651,9 +652,10 @@
                         ((inv.numColumns - 1) * cellWidthPx)));
                 availablePaddingX = (int) Math.min(availablePaddingX,
                         widthPx * MAX_HORIZONTAL_PADDING_PERCENT);
+                int hotseatVerticalPadding = isTaskbarPresent ? 0
+                        : hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx;
                 int availablePaddingY = Math.max(0, heightPx - edgeMarginPx - paddingBottom
-                        - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx
-                        - hotseatBarBottomPaddingPx);
+                        - (2 * inv.numRows * cellHeightPx) - hotseatVerticalPadding);
                 padding.set(availablePaddingX / 2, edgeMarginPx + availablePaddingY / 2,
                         availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
             } else {
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
deleted file mode 100644
index e5aecf7..0000000
--- a/src/com/android/launcher3/FocusHelper.java
+++ /dev/null
@@ -1,567 +0,0 @@
-/*
- * Copyright (C) 2015 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;
-
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.SoundEffectConstants;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderPagedView;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.util.FocusLogic;
-import com.android.launcher3.util.Thunk;
-
-/**
- * A keyboard listener we set on all the workspace icons.
- */
-class IconKeyEventListener implements View.OnKeyListener {
-    @Override
-    public boolean onKey(View v, int keyCode, KeyEvent event) {
-        return FocusHelper.handleIconKeyEvent(v, keyCode, event);
-    }
-}
-
-/**
- * A keyboard listener we set on all the hotseat buttons.
- */
-class HotseatIconKeyEventListener implements View.OnKeyListener {
-    @Override
-    public boolean onKey(View v, int keyCode, KeyEvent event) {
-        return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event);
-    }
-}
-
-/**
- * A keyboard listener we set on full screen pages (e.g. custom content).
- */
-class FullscreenKeyEventListener implements View.OnKeyListener {
-    @Override
-    public boolean onKey(View v, int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
-                || keyCode == KeyEvent.KEYCODE_PAGE_DOWN || keyCode == KeyEvent.KEYCODE_PAGE_UP) {
-            // Handle the key event just like a workspace icon would in these cases. In this case,
-            // it will basically act as if there is a single icon in the top left (so you could
-            // think of the fullscreen page as a focusable fullscreen widget).
-            return FocusHelper.handleIconKeyEvent(v, keyCode, event);
-        }
-        return false;
-    }
-}
-
-/**
- * TODO: Reevaluate if this is still required
- */
-public class FocusHelper {
-
-    private static final String TAG = "FocusHelper";
-    private static final boolean DEBUG = false;
-
-    /**
-     * Handles key events in paged folder.
-     */
-    public static class PagedFolderKeyEventListener implements View.OnKeyListener {
-
-        private final Folder mFolder;
-
-        public PagedFolderKeyEventListener(Folder folder) {
-            mFolder = folder;
-        }
-
-        @Override
-        public boolean onKey(View v, int keyCode, KeyEvent e) {
-            boolean consume = FocusLogic.shouldConsume(keyCode);
-            if (e.getAction() == KeyEvent.ACTION_UP) {
-                return consume;
-            }
-            if (DEBUG) {
-                Log.v(TAG, String.format("Handle ALL Folders keyevent=[%s].",
-                        KeyEvent.keyCodeToString(keyCode)));
-            }
-
-            if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
-                if (FeatureFlags.IS_STUDIO_BUILD) {
-                    throw new IllegalStateException("Parent of the focused item is not supported.");
-                } else {
-                    return false;
-                }
-            }
-
-            // Initialize variables.
-            final ShortcutAndWidgetContainer itemContainer = (ShortcutAndWidgetContainer) v.getParent();
-            final CellLayout cellLayout = (CellLayout) itemContainer.getParent();
-
-            final int iconIndex = itemContainer.indexOfChild(v);
-            final FolderPagedView pagedView = (FolderPagedView) cellLayout.getParent();
-
-            final int pageIndex = pagedView.indexOfChild(cellLayout);
-            final int pageCount = pagedView.getPageCount();
-            final boolean isLayoutRtl = Utilities.isRtl(v.getResources());
-
-            int[][] matrix = FocusLogic.createSparseMatrix(cellLayout);
-            // Process focus.
-            int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
-                    pageCount, isLayoutRtl);
-            if (newIconIndex == FocusLogic.NOOP) {
-                handleNoopKey(keyCode, v);
-                return consume;
-            }
-            ShortcutAndWidgetContainer newParent = null;
-            View child = null;
-
-            switch (newIconIndex) {
-                case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
-                case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
-                    newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
-                    if (newParent != null) {
-                        int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
-                        pagedView.snapToPage(pageIndex - 1);
-                        child = newParent.getChildAt(
-                                ((newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN)
-                                    ^ newParent.invertLayoutHorizontally()) ? 0 : matrix.length - 1,
-                                row);
-                    }
-                    break;
-                case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
-                    newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
-                    if (newParent != null) {
-                        pagedView.snapToPage(pageIndex - 1);
-                        child = newParent.getChildAt(0, 0);
-                    }
-                    break;
-                case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
-                    newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
-                    if (newParent != null) {
-                        pagedView.snapToPage(pageIndex - 1);
-                        child = newParent.getChildAt(matrix.length - 1, matrix[0].length - 1);
-                    }
-                    break;
-                case FocusLogic.NEXT_PAGE_FIRST_ITEM:
-                    newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
-                    if (newParent != null) {
-                        pagedView.snapToPage(pageIndex + 1);
-                        child = newParent.getChildAt(0, 0);
-                    }
-                    break;
-                case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
-                case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
-                    newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
-                    if (newParent != null) {
-                        pagedView.snapToPage(pageIndex + 1);
-                        child = FocusLogic.getAdjacentChildInNextFolderPage(
-                                newParent, v, newIconIndex);
-                    }
-                    break;
-                case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
-                    child = cellLayout.getChildAt(0, 0);
-                    break;
-                case FocusLogic.CURRENT_PAGE_LAST_ITEM:
-                    child = pagedView.getLastItem();
-                    break;
-                default: // Go to some item on the current page.
-                    child = itemContainer.getChildAt(newIconIndex);
-                    break;
-            }
-            if (child != null) {
-                child.requestFocus();
-                playSoundEffect(keyCode, v);
-            } else {
-                handleNoopKey(keyCode, v);
-            }
-            return consume;
-        }
-
-        public void handleNoopKey(int keyCode, View v) {
-            if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
-                mFolder.mFolderName.requestFocus();
-                playSoundEffect(keyCode, v);
-            }
-        }
-    }
-
-    /**
-     * Handles key events in the workspace hotseat (bottom of the screen).
-     * <p>Currently we don't special case for the phone UI in different orientations, even though
-     * the hotseat is on the side in landscape mode. This is to ensure that accessibility
-     * consistency is maintained across rotations.
-     */
-    static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e) {
-        boolean consume = FocusLogic.shouldConsume(keyCode);
-        if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
-            return consume;
-        }
-
-        final Launcher launcher = Launcher.getLauncher(v.getContext());
-        final DeviceProfile profile = launcher.getDeviceProfile();
-
-        if (DEBUG) {
-            Log.v(TAG, String.format(
-                    "Handle HOTSEAT BUTTONS keyevent=[%s] on hotseat buttons, isVertical=%s",
-                    KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
-        }
-
-        // Initialize the variables.
-        final Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
-        final ShortcutAndWidgetContainer hotseatParent = (ShortcutAndWidgetContainer) v.getParent();
-        final CellLayout hotseatLayout = (CellLayout) hotseatParent.getParent();
-
-        final ItemInfo itemInfo = (ItemInfo) v.getTag();
-        int pageIndex = workspace.getNextPage();
-        int pageCount = workspace.getChildCount();
-        int iconIndex = hotseatParent.indexOfChild(v);
-        int iconRank = ((CellLayout.LayoutParams) hotseatLayout.getShortcutsAndWidgets()
-                .getChildAt(iconIndex).getLayoutParams()).cellX;
-
-        final CellLayout iconLayout = (CellLayout) workspace.getChildAt(pageIndex);
-        if (iconLayout == null) {
-            // This check is to guard against cases where key strokes rushes in when workspace
-            // child creation/deletion is still in flux. (e.g., during drop or fling
-            // animation.)
-            return consume;
-        }
-        final ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
-
-        ViewGroup parent = null;
-        int[][] matrix = null;
-
-        if (keyCode == KeyEvent.KEYCODE_DPAD_UP &&
-                !profile.isVerticalBarLayout()) {
-            matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
-            iconIndex += iconParent.getChildCount();
-            parent = iconParent;
-        } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT &&
-                profile.isVerticalBarLayout()) {
-            matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
-            iconIndex += iconParent.getChildCount();
-            parent = iconParent;
-        } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
-                profile.isVerticalBarLayout()) {
-            keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
-        } else {
-            // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
-            // matrix extended with hotseat.
-            matrix = FocusLogic.createSparseMatrix(hotseatLayout);
-            parent = hotseatParent;
-        }
-
-        // Process the focus.
-        int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
-                pageCount, Utilities.isRtl(v.getResources()));
-
-        View newIcon = null;
-        switch (newIconIndex) {
-            case FocusLogic.NEXT_PAGE_FIRST_ITEM:
-                parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
-                newIcon = parent.getChildAt(0);
-                // TODO(hyunyoungs): handle cases where the child is not an icon but
-                // a folder or a widget.
-                workspace.snapToPage(pageIndex + 1);
-                break;
-            case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
-                parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
-                newIcon = parent.getChildAt(0);
-                // TODO(hyunyoungs): handle cases where the child is not an icon but
-                // a folder or a widget.
-                workspace.snapToPage(pageIndex - 1);
-                break;
-            case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
-                parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
-                newIcon = parent.getChildAt(parent.getChildCount() - 1);
-                // TODO(hyunyoungs): handle cases where the child is not an icon but
-                // a folder or a widget.
-                workspace.snapToPage(pageIndex - 1);
-                break;
-            case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
-            case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
-                // Go to the previous page but keep the focus on the same hotseat icon.
-                workspace.snapToPage(pageIndex - 1);
-                break;
-            case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
-            case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
-                // Go to the next page but keep the focus on the same hotseat icon.
-                workspace.snapToPage(pageIndex + 1);
-                break;
-        }
-        if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) {
-            newIconIndex -= iconParent.getChildCount();
-        }
-        if (parent != null) {
-            if (newIcon == null && newIconIndex >= 0) {
-                newIcon = parent.getChildAt(newIconIndex);
-            }
-            if (newIcon != null) {
-                newIcon.requestFocus();
-                playSoundEffect(keyCode, v);
-            }
-        }
-        return consume;
-    }
-
-    /**
-     * Handles key events in a workspace containing icons.
-     */
-    static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
-        boolean consume = FocusLogic.shouldConsume(keyCode);
-        if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
-            return consume;
-        }
-
-        Launcher launcher = Launcher.getLauncher(v.getContext());
-        DeviceProfile profile = launcher.getDeviceProfile();
-
-        if (DEBUG) {
-            Log.v(TAG, String.format("Handle WORKSPACE ICONS keyevent=[%s] isVerticalBar=%s",
-                    KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
-        }
-
-        // Initialize the variables.
-        ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
-        CellLayout iconLayout = (CellLayout) parent.getParent();
-        final Workspace workspace = (Workspace) iconLayout.getParent();
-        final ViewGroup dragLayer = (ViewGroup) workspace.getParent();
-        final ViewGroup tabs = (ViewGroup) dragLayer.findViewById(R.id.drop_target_bar);
-        final Hotseat hotseat = (Hotseat) dragLayer.findViewById(R.id.hotseat);
-
-        final ItemInfo itemInfo = (ItemInfo) v.getTag();
-        final int iconIndex = parent.indexOfChild(v);
-        final int pageIndex = workspace.indexOfChild(iconLayout);
-        final int pageCount = workspace.getChildCount();
-
-        CellLayout hotseatLayout = (CellLayout) hotseat.getChildAt(0);
-        ShortcutAndWidgetContainer hotseatParent = hotseatLayout.getShortcutsAndWidgets();
-        int[][] matrix;
-
-        // KEYCODE_DPAD_DOWN in portrait (KEYCODE_DPAD_RIGHT in landscape) is the only key allowed
-        // to take a user to the hotseat. For other dpad navigation, do not use the matrix extended
-        // with the hotseat.
-        if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && !profile.isVerticalBarLayout()) {
-            matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
-        } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
-                profile.isVerticalBarLayout()) {
-            matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
-        } else {
-            matrix = FocusLogic.createSparseMatrix(iconLayout);
-        }
-
-        // Process the focus.
-        int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
-                pageCount, Utilities.isRtl(v.getResources()));
-        boolean isRtl = Utilities.isRtl(v.getResources());
-        View newIcon = null;
-        CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex);
-        switch (newIconIndex) {
-            case FocusLogic.NOOP:
-                if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
-                    newIcon = tabs;
-                }
-                break;
-            case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
-            case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
-                int newPageIndex = pageIndex - 1;
-                if (newIconIndex == FocusLogic.NEXT_PAGE_RIGHT_COLUMN) {
-                    newPageIndex = pageIndex + 1;
-                }
-                int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
-                parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
-                if (parent != null) {
-                    iconLayout = (CellLayout) parent.getParent();
-                    matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout,
-                            iconLayout.getCountX(), row);
-                    newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
-                            newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
-                    if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
-                        newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
-                                isRtl);
-                    } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
-                        newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
-                                isRtl);
-                    } else {
-                        newIcon = parent.getChildAt(newIconIndex);
-                    }
-                }
-                break;
-            case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
-                workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
-                newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
-                if (newIcon == null) {
-                    // Check the hotseat if no focusable item was found on the workspace.
-                    newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
-                    workspace.snapToPage(pageIndex - 1);
-                }
-                break;
-            case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
-                newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, isRtl);
-                break;
-            case FocusLogic.NEXT_PAGE_FIRST_ITEM:
-                newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, isRtl);
-                break;
-            case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
-            case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
-                newPageIndex = pageIndex + 1;
-                if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) {
-                    newPageIndex = pageIndex - 1;
-                }
-                row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
-                parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
-                if (parent != null) {
-                    iconLayout = (CellLayout) parent.getParent();
-                    matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout, -1, row);
-                    newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
-                            newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
-                    if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
-                        newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
-                                isRtl);
-                    } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
-                        newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
-                                isRtl);
-                    } else {
-                        newIcon = parent.getChildAt(newIconIndex);
-                    }
-                }
-                break;
-            case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
-                newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
-                if (newIcon == null) {
-                    // Check the hotseat if no focusable item was found on the workspace.
-                    newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
-                }
-                break;
-            case FocusLogic.CURRENT_PAGE_LAST_ITEM:
-                newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
-                if (newIcon == null) {
-                    // Check the hotseat if no focusable item was found on the workspace.
-                    newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout, isRtl);
-                }
-                break;
-            default:
-                // current page, some item.
-                if (0 <= newIconIndex && newIconIndex < parent.getChildCount()) {
-                    newIcon = parent.getChildAt(newIconIndex);
-                } else if (parent.getChildCount() <= newIconIndex &&
-                        newIconIndex < parent.getChildCount() + hotseatParent.getChildCount()) {
-                    newIcon = hotseatParent.getChildAt(newIconIndex - parent.getChildCount());
-                }
-                break;
-        }
-        if (newIcon != null) {
-            newIcon.requestFocus();
-            playSoundEffect(keyCode, v);
-        }
-        return consume;
-    }
-
-    //
-    // Helper methods.
-    //
-
-    /**
-     * Private helper method to get the CellLayoutChildren given a CellLayout index.
-     */
-    @Thunk static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
-            ViewGroup container, int i) {
-        CellLayout parent = (CellLayout) container.getChildAt(i);
-        return parent.getShortcutsAndWidgets();
-    }
-
-    /**
-     * Helper method to be used for playing sound effects.
-     */
-    @Thunk static void playSoundEffect(int keyCode, View v) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_DPAD_LEFT:
-                v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
-                break;
-            case KeyEvent.KEYCODE_DPAD_RIGHT:
-                v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
-                break;
-            case KeyEvent.KEYCODE_DPAD_DOWN:
-            case KeyEvent.KEYCODE_PAGE_DOWN:
-            case KeyEvent.KEYCODE_MOVE_END:
-                v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
-                break;
-            case KeyEvent.KEYCODE_DPAD_UP:
-            case KeyEvent.KEYCODE_PAGE_UP:
-            case KeyEvent.KEYCODE_MOVE_HOME:
-                v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
-                break;
-            default:
-                break;
-        }
-    }
-
-    private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout,
-            int pageIndex, boolean isRtl) {
-        if (pageIndex - 1 < 0) {
-            return null;
-        }
-        CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
-        View newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
-        if (newIcon == null) {
-            // Check the hotseat if no focusable item was found on the workspace.
-            newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout,isRtl);
-            workspace.snapToPage(pageIndex - 1);
-        }
-        return newIcon;
-    }
-
-    private static View handleNextPageFirstItem(Workspace workspace, CellLayout hotseatLayout,
-            int pageIndex, boolean isRtl) {
-        if (pageIndex + 1 >= workspace.getPageCount()) {
-            return null;
-        }
-        CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex + 1);
-        View newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
-        if (newIcon == null) {
-            // Check the hotseat if no focusable item was found on the workspace.
-            newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
-            workspace.snapToPage(pageIndex + 1);
-        }
-        return newIcon;
-    }
-
-    private static View getFirstFocusableIconInReadingOrder(CellLayout cellLayout, boolean isRtl) {
-        View icon;
-        int countX = cellLayout.getCountX();
-        for (int y = 0; y < cellLayout.getCountY(); y++) {
-            int increment = isRtl ? -1 : 1;
-            for (int x = isRtl ? countX - 1 : 0; 0 <= x && x < countX; x += increment) {
-                if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
-                    return icon;
-                }
-            }
-        }
-        return null;
-    }
-
-    private static View getFirstFocusableIconInReverseReadingOrder(CellLayout cellLayout,
-            boolean isRtl) {
-        View icon;
-        int countX = cellLayout.getCountX();
-        for (int y = cellLayout.getCountY() - 1; y >= 0; y--) {
-            int increment = isRtl ? 1 : -1;
-            for (int x = isRtl ? 0 : countX - 1; 0 <= x && x < countX; x += increment) {
-                if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
-                    return icon;
-                }
-            }
-        }
-        return null;
-    }
-}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 4f4f2a7..b2112ad 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -21,6 +21,7 @@
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -98,14 +99,20 @@
         } else {
             lp.gravity = Gravity.BOTTOM;
             lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
-            lp.height = grid.hotseatBarSizePx + insets.bottom;
+            lp.height = grid.isTaskbarPresent
+                    ? grid.taskbarSize
+                    : grid.hotseatBarSizePx + insets.bottom;
         }
-        Rect padding = grid.getHotseatLayoutPadding();
-        int paddingBottom = padding.bottom;
-        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && !grid.isVerticalBarLayout()) {
-            paddingBottom -= grid.hotseatBarBottomPaddingPx;
+        if (!grid.isTaskbarPresent) {
+            // When taskbar is present, we set the padding separately to ensure a seamless visual
+            // handoff between taskbar and hotseat during drag and drop.
+            Rect padding = grid.getHotseatLayoutPadding();
+            int paddingBottom = padding.bottom;
+            if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && !grid.isVerticalBarLayout()) {
+                paddingBottom -= grid.hotseatBarBottomPaddingPx;
+            }
+            setPadding(padding.left, padding.top, padding.right, paddingBottom);
         }
-        setPadding(padding.left, padding.top, padding.right, paddingBottom);
 
         setLayoutParams(lp);
         InsettableFrameLayout.dispatchInsets(this, insets);
@@ -160,4 +167,11 @@
     protected void showInlineQsb() {
         //Does nothing
     }
+
+    /**
+     * Returns the first View for which the given itemOperator returns true, or null.
+     */
+    public View getFirstItemMatch(Workspace.ItemOperator itemOperator) {
+        return mWorkspace.getFirstMatch(new CellLayout[] { this }, itemOperator);
+    }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 253a7c7..80c80d7 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -121,6 +121,7 @@
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.folder.FolderIcon;
@@ -2760,8 +2761,10 @@
         return super.onKeyUp(keyCode, event);
     }
 
-    protected StateHandler<LauncherState>[] createStateHandlers() {
-        return new StateHandler[] { getAllAppsController(), getWorkspace() };
+    @Override
+    protected void collectStateHandlers(List<StateHandler> out) {
+        out.add(getAllAppsController());
+        out.add(getWorkspace());
     }
 
     public TouchController[] createTouchControllers() {
@@ -2863,6 +2866,10 @@
         return false;
     }
 
+    public DragOptions getDefaultWorkspaceDragOptions() {
+        return new DragOptions();
+    }
+
     private static class NonConfigInstance {
         public Configuration config;
         public Bitmap snapshot;
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index dfdc53c..22c257a 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -193,6 +193,7 @@
         public static final int CONTAINER_DESKTOP = -100;
         public static final int CONTAINER_HOTSEAT = -101;
         public static final int CONTAINER_PREDICTION = -102;
+        public static final int CONTAINER_WIDGETS_PREDICTION = -111;
         public static final int CONTAINER_HOTSEAT_PREDICTION = -103;
         public static final int CONTAINER_ALL_APPS = -104;
         public static final int CONTAINER_WIDGETS_TRAY = -105;
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 781f171..21c40ef 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -59,6 +59,7 @@
     public static final int OVERVIEW_ACTIONS = 1 << 6;
     public static final int TASKBAR = 1 << 7;
     public static final int CLEAR_ALL_BUTTON = 1 << 8;
+    public static final int WORKSPACE_PAGE_INDICATOR = 1 << 9;
 
     /** Mask of all the items that are contained in the apps view. */
     public static final int APPS_VIEW_ITEM_MASK =
@@ -188,15 +189,26 @@
     }
 
     public int getVisibleElements(Launcher launcher) {
-        int flags = HOTSEAT_ICONS | VERTICAL_SWIPE_INDICATOR | TASKBAR;
-        if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()
-                && !launcher.getDeviceProfile().isVerticalBarLayout()) {
+        DeviceProfile deviceProfile = launcher.getDeviceProfile();
+        int flags = WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR | TASKBAR;
+        if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get() && !deviceProfile.isVerticalBarLayout()) {
             flags |= HOTSEAT_SEARCH_BOX;
         }
+        if (!deviceProfile.isTaskbarPresent) {
+            flags |= HOTSEAT_ICONS;
+        }
         return flags;
     }
 
     /**
+     * A shorthand for checking getVisibleElements() & elements == elements.
+     * @return Whether all of the given elements are visible.
+     */
+    public boolean areElementsVisible(Launcher launcher, int elements) {
+        return (getVisibleElements(launcher) & elements) == elements;
+    }
+
+    /**
      * Fraction shift in the vertical translation UI and related properties
      *
      * @see com.android.launcher3.allapps.AllAppsTransitionController
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index c6766a4..50f1e44 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -96,8 +96,6 @@
     private static final int MIN_FLING_VELOCITY = 250;
 
     private boolean mFreeScroll = false;
-    /** If {@code false}, disable swipe gesture to switch between pages. */
-    private boolean mSwipeGestureEnabled = true;
 
     protected final int mFlingThresholdVelocity;
     protected final int mEasyFlingThresholdVelocity;
@@ -860,14 +858,6 @@
     }
 
     /**
-     * If {@code enableSwipeGesture} is {@code true}, enables swipe gesture to navigate between
-     * pages. Otherwise, disables the navigation gesture.
-     */
-    public void setSwipeGestureEnabled(boolean swipeGestureEnabled) {
-        mSwipeGestureEnabled = swipeGestureEnabled;
-    }
-
-    /**
      * {@inheritDoc}
      */
     @Override
@@ -889,8 +879,6 @@
          * scrolling there.
          */
 
-        if (!mSwipeGestureEnabled) return false;
-
         // Skip touch handling if there are no pages to swipe
         if (getChildCount() <= 0) return false;
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 981ead9..87fb6fb 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -446,6 +446,19 @@
         }
 
         updateChildrenLayersEnabled();
+        StateManager<LauncherState> stateManager = mLauncher.getStateManager();
+        stateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
+            @Override
+            public void onStateTransitionComplete(LauncherState finalState) {
+                if (finalState == NORMAL) {
+                    if (!mDeferRemoveExtraEmptyScreen) {
+                        removeExtraEmptyScreen(true /* stripEmptyScreens */);
+                    }
+                    stateManager.removeStateListener(this);
+                }
+            }
+        });
+
         mDragInfo = null;
         mOutlineProvider = null;
         mDragSourceInternal = null;
@@ -1877,19 +1890,6 @@
                             };
                         }
                     }
-                    StateManager<LauncherState> stateManager = mLauncher.getStateManager();
-                    stateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
-                        @Override
-                        public void onStateTransitionComplete(LauncherState finalState) {
-                            if (finalState == NORMAL) {
-                                if (!mDeferRemoveExtraEmptyScreen) {
-                                    removeExtraEmptyScreen(true /* stripEmptyScreens */);
-                                }
-                                stateManager.removeStateListener(this);
-                            }
-                        }
-                    });
-
                     mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
                             lp.cellX, lp.cellY, item.spanX, item.spanY);
                 } else {
@@ -2973,7 +2973,7 @@
      * @param operators List of operators, in order starting from best matching operator.
      * @return
      */
-    private View getFirstMatch(CellLayout[] cellLayouts, final ItemOperator... operators) {
+    View getFirstMatch(CellLayout[] cellLayouts, final ItemOperator... operators) {
         // This array is filled with the first match for each operator.
         final View[] matches = new View[operators.length];
         // For efficiency, the outer loop should be CellLayout.
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 0e0ddfb..660eeab 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -28,11 +28,13 @@
 import static com.android.launcher3.LauncherState.HINT_STATE;
 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.WORKSPACE_PAGE_INDICATOR;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.ZOOM_OUT;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
 import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
@@ -141,9 +143,11 @@
             }
 
             float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
-            propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, fadeInterpolator);
+            propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha,
+                    config.getInterpolator(ANIM_HOTSEAT_FADE, fadeInterpolator));
+            float workspacePageIndicatorAlpha = (elements & WORKSPACE_PAGE_INDICATOR) != 0 ? 1 : 0;
             propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
-                    hotseatIconsAlpha, fadeInterpolator);
+                    workspacePageIndicatorAlpha, fadeInterpolator);
         }
 
         if (config.onlyPlayAtomicComponent()) {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index a92e1aa..edd9a9f 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -199,7 +199,9 @@
                 break;
             }
         }
-        rebindAdapters(hasWorkApps);
+        if (!mAH[AdapterHolder.MAIN].appsList.hasFilter()) {
+            rebindAdapters(hasWorkApps);
+        }
         if (hasWorkApps) {
             resetWorkProfile();
         }
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 5030c5e..bb175ea 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -240,20 +240,18 @@
         @Override
         public int getSpanSize(int position) {
             int viewType = mApps.getAdapterItems().get(position).viewType;
+            int totalSpans = mGridLayoutMgr.getSpanCount();
             if (isIconViewType(viewType)) {
-                return 1 * SPAN_MULTIPLIER;
+                return totalSpans / mAppsPerRow;
             } else if (mSearchAdapterProvider.isSearchView(viewType)) {
-                return mSearchAdapterProvider.getGridSpanSize(viewType, mAppsPerRow);
+                return totalSpans / mSearchAdapterProvider.getItemsPerRow(viewType, mAppsPerRow);
             } else {
                 // Section breaks span the full width
-                return mAppsPerRow * SPAN_MULTIPLIER;
+                return totalSpans;
             }
         }
     }
 
-    // multiplier to support adapter item column count that is not mAppsPerRow.
-    public static final int SPAN_MULTIPLIER = 3;
-
     private final BaseDraggingActivity mLauncher;
     private final LayoutInflater mLayoutInflater;
     private final AlphabeticalAppsList mApps;
@@ -285,14 +283,19 @@
 
         mOnIconClickListener = launcher.getItemOnClickListener();
 
-        setAppsPerRow(mLauncher.getDeviceProfile().inv.numAllAppsColumns);
-
         mSearchAdapterProvider = searchAdapterProvider;
+        setAppsPerRow(mLauncher.getDeviceProfile().inv.numAllAppsColumns);
     }
 
     public void setAppsPerRow(int appsPerRow) {
         mAppsPerRow = appsPerRow;
-        mGridLayoutMgr.setSpanCount(mAppsPerRow * SPAN_MULTIPLIER);
+        int totalSpans = mAppsPerRow;
+        for (int itemPerRow : mSearchAdapterProvider.getSupportedItemsPerRowArray()) {
+            if (totalSpans % itemPerRow != 0) {
+                totalSpans *= itemPerRow;
+            }
+        }
+        mGridLayoutMgr.setSpanCount(totalSpans);
     }
 
     /**
diff --git a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
deleted file mode 100644
index b34c8b8..0000000
--- a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps;
-
-import android.annotation.TargetApi;
-import android.graphics.Insets;
-import android.os.Build;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowInsets;
-import android.view.WindowInsetsAnimationControlListener;
-import android.view.WindowInsetsAnimationController;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.UiThreadHelper;
-
-/**
- * Handles IME over all apps to be synchronously transitioning along with the passed in
- * root inset.
- */
-public class AllAppsInsetTransitionController {
-
-    private static final boolean DEBUG = true;
-    private static final String TAG = "AllAppsInsetTransitionController";
-    private static final Interpolator LINEAR = new LinearInterpolator();
-
-    private WindowInsetsAnimationController mAnimationController;
-    private WindowInsetsAnimationControlListener mCurrentRequest;
-
-    private Runnable mSearchEduRunnable;
-
-    private float mAllAppsHeight;
-
-    private int mDownInsetBottom;
-    private boolean mShownAtDown;
-
-    private int mHiddenInsetBottom;
-    private int mShownInsetBottom;
-
-    private float mDown, mCurrent;
-    private View mApps;
-
-    /**
-     *
-     */
-    public boolean showSearchEduIfNecessary() {
-        if (mSearchEduRunnable == null) {
-            return false;
-        }
-        mSearchEduRunnable.run();
-        return true;
-    }
-
-    public void setSearchEduRunnable(Runnable eduRunnable) {
-        mSearchEduRunnable = eduRunnable;
-    }
-
-    // Only purpose of these states is to keep track of fast fling transition
-    enum State {
-        RESET, DRAG_START_BOTTOM, DRAG_START_BOTTOM_IME_CANCELLED,
-        FLING_END_TOP, FLING_END_TOP_IME_CANCELLED,
-        DRAG_START_TOP, FLING_END_BOTTOM
-    }
-
-    private State mState;
-
-    public AllAppsInsetTransitionController(float allAppsHeight, View appsView) {
-        mAllAppsHeight = allAppsHeight;
-        mApps = appsView;
-    }
-
-    public void show() {
-        mApps.getWindowInsetsController().show(WindowInsets.Type.ime());
-    }
-
-    public void hide() {
-        if (!Utilities.ATLEAST_R) return;
-
-        WindowInsets insets = mApps.getRootWindowInsets();
-        if (insets == null) return;
-
-        boolean imeVisible = insets.isVisible(WindowInsets.Type.ime());
-
-        if (DEBUG) {
-            Log.d(TAG, "\nhide imeVisible=" + imeVisible);
-        }
-        if (insets.isVisible(WindowInsets.Type.ime())) {
-            mApps.getWindowInsetsController().hide(WindowInsets.Type.ime());
-        }
-    }
-
-    /**
-     * Initializes member variables and requests for the {@link WindowInsetsAnimationController}
-     * object.
-     *
-     * @param progress value between 0..1
-     */
-    @TargetApi(Build.VERSION_CODES.R)
-    public void onDragStart(float progress) {
-        if (!Utilities.ATLEAST_R) return;
-
-        // Until getRootWindowInsets().isVisible(...) method returns correct value,
-        // only support InsetController based IME transition during swipe up and
-        // NOT swipe down
-        if (Float.compare(progress, 0f) == 0) return;
-
-        setState(true, false, progress);
-        mDown = progress * mAllAppsHeight;
-
-        // Below two values are sometimes incorrect. Possibly a platform bug
-        // mDownInsetBottom = mApps.getRootWindowInsets().getInsets(WindowInsets.Type.ime()).bottom;
-        // mShownAtDown = mApps.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
-
-        if (DEBUG) {
-            Log.d(TAG, "\nonDragStart progress=" + progress
-                    + " mDownInsets=" + mDownInsetBottom
-                    + " mShownAtDown=" + mShownAtDown);
-        }
-
-        mApps.getWindowInsetsController().controlWindowInsetsAnimation(
-                WindowInsets.Type.ime(), -1 /* no predetermined duration */, LINEAR, null,
-                mCurrentRequest = new WindowInsetsAnimationControlListener() {
-
-                    @Override
-                    public void onReady(WindowInsetsAnimationController controller, int types) {
-                        if (DEBUG) {
-                            Log.d(TAG, "Listener.onReady " + (mCurrentRequest == this));
-                        }
-                        if (controller != null) {
-                            if (mCurrentRequest == this && !handleFinishOnFling(controller)) {
-                                mAnimationController = controller;
-                            } else {
-                                controller.finish(false /* just don't show */);
-                            }
-                        }
-                    }
-
-                    @Override
-                    public void onFinished(WindowInsetsAnimationController controller) {
-                        // when screen lock happens, then this method get called
-                        if (DEBUG) {
-                            Log.d(TAG, "Listener.onFinished ctrl=" + controller
-                                    + " mAnimationController=" + mAnimationController);
-                        }
-                        if (mAnimationController != null) {
-                            mAnimationController.finish(true);
-                            mAnimationController = null;
-                        }
-                    }
-
-                    @Override
-                    public void onCancelled(@Nullable WindowInsetsAnimationController controller) {
-                        if (DEBUG) {
-                            // Keep the verbose logging to chase down IME not showing up issue.
-                            // b/178904132
-                            Log.e(TAG, "Listener.onCancelled ctrl=" + controller
-                                    + " mAnimationController=" + mAnimationController,
-                                    new Exception());
-                        }
-                        if (mState == State.DRAG_START_BOTTOM) {
-                            mState = State.DRAG_START_BOTTOM_IME_CANCELLED;
-                        }
-                        mAnimationController = null;
-                        if (controller != null) {
-                            controller.finish(true);
-                        }
-                    }
-                });
-    }
-
-    /**
-     * If IME bounds after touch sequence finishes, call finish.
-     */
-    private boolean handleFinishOnFling(WindowInsetsAnimationController controller) {
-        if (!Utilities.ATLEAST_R) return false;
-
-        if (mState == State.FLING_END_TOP) {
-            controller.finish(true);
-            return true;
-        } else if (mState == State.FLING_END_BOTTOM) {
-            controller.finish(false);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Handles the translation using the progress.
-     *
-     * @param progress value between 0..1
-     */
-    @TargetApi(Build.VERSION_CODES.R)
-    public void setProgress(float progress) {
-        if (!Utilities.ATLEAST_R) return;
-        // progress that equals to 0 or 1 is error prone. Do not use them.
-        // Instead use onDragStart and onAnimationEnd
-        if (mAnimationController == null || progress <= 0f || progress >= 1f) return;
-
-        mCurrent = progress * mAllAppsHeight;
-        mHiddenInsetBottom = mAnimationController.getHiddenStateInsets().bottom; // 0
-        mShownInsetBottom = mAnimationController.getShownStateInsets().bottom; // 1155
-
-        int shift = mShownAtDown ? 0 : (int) (mAllAppsHeight - mShownInsetBottom);
-
-        int inset = (int) (mDownInsetBottom + (mDown - mCurrent) - shift);
-
-        final int start = mShownAtDown ? mShownInsetBottom : mHiddenInsetBottom;
-        final int end = mShownAtDown ? mHiddenInsetBottom : mShownInsetBottom;
-        inset = Math.max(inset, mHiddenInsetBottom);
-        inset = Math.min(inset, mShownInsetBottom);
-        if (DEBUG && false) {
-            Log.d(TAG, "updateInset mCurrent=" + mCurrent + " mDown="
-                    + mDown + " hidden=" + mHiddenInsetBottom
-                    + " shown=" + mShownInsetBottom
-                    + " mDownInsets.bottom=" + mDownInsetBottom + " inset=" + inset
-                    + " shift= " + shift);
-        }
-
-        mAnimationController.setInsetsAndAlpha(
-                Insets.of(0, 0, 0, inset),
-                1f, (inset - start) / (float) (end - start));
-    }
-
-    /**
-     * Report to the animation controller that we no longer plan to translate anymore.
-     *
-     * @param progress value between 0..1
-     */
-    @TargetApi(Build.VERSION_CODES.R)
-    public void onAnimationEnd(float progress) {
-        if (DEBUG) {
-            Log.d(TAG, "onAnimationEnd progress=" + progress
-                    + " mAnimationController=" + mAnimationController);
-        }
-        if (mState == null) {
-            // only called when launcher restarting.
-            UiThreadHelper.hideKeyboardAsync(mApps.getContext(), mApps.getWindowToken());
-        }
-
-        setState(false, true, progress);
-
-
-        if (mAnimationController == null) {
-            if (mState == State.FLING_END_TOP_IME_CANCELLED) {
-                mApps.getWindowInsetsController().show(WindowInsets.Type.ime());
-            }
-            return;
-        }
-
-        /* handle finish */
-        if (mState == State.FLING_END_TOP) {
-            mAnimationController.finish(true /* show */);
-        } else {
-            if (Float.compare(progress, 1f) == 0 /* bottom */) {
-                mAnimationController.finish(false /* gone */);
-            } else {
-                mAnimationController.finish(mShownAtDown);
-            }
-        }
-        /* handle finish */
-
-        if (DEBUG) {
-            Log.d(TAG, "endTranslation progress=" + progress
-                    + " mAnimationController=" + mAnimationController);
-        }
-        mAnimationController = null;
-        mCurrentRequest = null;
-        setState(false, false, progress);
-    }
-
-    private void setState(boolean start, boolean end, float progress) {
-        State state = State.RESET;
-        if (start && end) {
-            throw new IllegalStateException("drag start and end cannot happen in same call");
-        }
-        if (start) {
-            if (Float.compare(progress, 1f) == 0) {
-                state = State.DRAG_START_BOTTOM;
-            } else if (Float.compare(progress, 0f) == 0) {
-                state = State.DRAG_START_TOP;
-            }
-        } else if (end) {
-            if (Float.compare(progress, 1f) == 0 && mState == State.DRAG_START_TOP) {
-                state = State.FLING_END_BOTTOM;
-            } else if (Float.compare(progress, 0f) == 0) {
-                if (mState == State.DRAG_START_BOTTOM) {
-                    state = State.FLING_END_TOP;
-                } else if (mState == State.DRAG_START_BOTTOM_IME_CANCELLED) {
-                    state = State.FLING_END_TOP_IME_CANCELLED;
-                }
-            }
-        }
-        if (DEBUG) {
-            Log.d(TAG, "setState " + mState + " -> " + state);
-        }
-        mState = state;
-    }
-}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index ace9938..179cb77 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -19,6 +19,9 @@
 import static android.view.View.MeasureSpec.UNSPECIFIED;
 import static android.view.View.MeasureSpec.makeMeasureSpec;
 
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
@@ -36,6 +39,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
 import java.util.ArrayList;
@@ -176,6 +180,23 @@
     }
 
     @Override
+    public void onScrollStateChanged(int state) {
+        super.onScrollStateChanged(state);
+
+        StatsLogManager mgr = BaseDraggingActivity.fromContext(getContext()).getStatsLogManager();
+        switch (state) {
+            case SCROLL_STATE_DRAGGING:
+                mgr.logger().sendToInteractionJankMonitor(
+                        LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN, this);
+                break;
+            case SCROLL_STATE_IDLE:
+                mgr.logger().sendToInteractionJankMonitor(
+                        LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END, this);
+                break;
+        }
+    }
+
+    @Override
     public boolean onInterceptTouchEvent(MotionEvent e) {
         boolean result = super.onInterceptTouchEvent(e);
         if (!result && e.getAction() == MotionEvent.ACTION_DOWN
diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
index 9328a3d..f4d735e 100644
--- a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
+++ b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
@@ -48,7 +48,6 @@
     @Override
     public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
         List<AllAppsGridAdapter.AdapterItem> adapterItems = mAppsView.getApps().getAdapterItems();
-        boolean drawFallbackFocusedView = true;
         for (int i = 0; i < parent.getChildCount(); i++) {
             View view = parent.getChildAt(i);
             int position = parent.getChildAdapterPosition(view);
@@ -60,28 +59,12 @@
                     decorationHandler.extendBounds(view);
                     if (sectionInfo.isFocusedView()) {
                         decorationHandler.onFocusDraw(c, view);
-                        drawFallbackFocusedView = false;
                     } else {
                         decorationHandler.onGroupDraw(c);
                     }
                 }
             }
         }
-        // fallback logic in case none of the SearchTarget is labeled as focused item
-        if (drawFallbackFocusedView) {
-            for (int i = 0; i < parent.getChildCount(); i++) {
-                View view = parent.getChildAt(i);
-                int position = parent.getChildAdapterPosition(view);
-                AllAppsGridAdapter.AdapterItem adapterItem = adapterItems.get(position);
-                if (adapterItem.sectionDecorationInfo != null) {
-                    SectionDecorationInfo sectionInfo = adapterItem.sectionDecorationInfo;
-                    SectionDecorationHandler decorationHandler = sectionInfo.getDecorationHandler();
-                    if (decorationHandler != null) {
-                        drawDecoration(c, decorationHandler, parent);
-                    }
-                }
-            }
-        }
     }
 
     // Fallback logic in case non of the SearchTarget is labeled as focused item.
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index dc58c99..abf63dc 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -33,18 +33,15 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
-import android.content.SharedPreferences;
 import android.util.FloatProperty;
 import android.view.View;
 import android.view.animation.Interpolator;
-import android.widget.EditText;
-
-import androidx.core.os.BuildCompat;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
@@ -63,8 +60,8 @@
  * If release velocity < THRES1, snap according to either top or bottom depending on whether it's
  * closer to top or closer to the page indicator.
  */
-public class AllAppsTransitionController implements StateHandler<LauncherState>,
-        OnDeviceProfileChangeListener, SharedPreferences.OnSharedPreferenceChangeListener {
+public class AllAppsTransitionController
+        implements StateHandler<LauncherState>, OnDeviceProfileChangeListener {
 
     public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
             new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
@@ -81,7 +78,6 @@
             };
 
     private static final int APPS_VIEW_ALPHA_CHANNEL_INDEX = 0;
-    private static final String PREF_KEY_SHOW_SEARCH_IME = "pref_search_show_ime";
 
     private AllAppsContainerView mAppsView;
     private ScrimView mScrimView;
@@ -99,8 +95,6 @@
     private float mProgress;        // [0, 1], mShiftRange * mProgress = shiftCurrent
 
     private float mScrollRangeDelta = 0;
-    private AllAppsInsetTransitionController mInsetController;
-    private boolean mSearchImeEnabled;
 
     public AllAppsTransitionController(Launcher l) {
         mLauncher = l;
@@ -109,19 +103,12 @@
 
         mIsVerticalLayout = mLauncher.getDeviceProfile().isVerticalBarLayout();
         mLauncher.addOnDeviceProfileChangeListener(this);
-
-        onSharedPreferenceChanged(mLauncher.getSharedPrefs(), PREF_KEY_SHOW_SEARCH_IME);
-        mLauncher.getSharedPrefs().registerOnSharedPreferenceChangeListener(this);
     }
 
     public float getShiftRange() {
         return mShiftRange;
     }
 
-    public AllAppsInsetTransitionController getInsetController() {
-        return mInsetController;
-    }
-
     @Override
     public void onDeviceProfileChanged(DeviceProfile dp) {
         mIsVerticalLayout = dp.isVerticalBarLayout();
@@ -144,13 +131,9 @@
      */
     public void setProgress(float progress) {
         mProgress = progress;
-        mScrimView.setProgress(progress);
-        float shiftCurrent = progress * mShiftRange;
 
-        mAppsView.setTranslationY(shiftCurrent);
-        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && mSearchImeEnabled) {
-            mInsetController.setProgress(progress);
-        }
+        mScrimView.setProgress(progress);
+        mAppsView.setTranslationY(progress * mShiftRange);
     }
 
     public float getProgress() {
@@ -239,8 +222,7 @@
     public void setupViews(AllAppsContainerView appsView, ScrimView scrimView) {
         mAppsView = appsView;
         mScrimView = scrimView;
-        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()) {
-            mInsetController = new AllAppsInsetTransitionController(mShiftRange, mAppsView);
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && Utilities.ATLEAST_R) {
             mLauncher.getSystemUiController().updateUiState(UI_STATE_ALLAPPS,
                     View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                             | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
@@ -267,23 +249,5 @@
         if (Float.compare(mProgress, 1f) == 0) {
             mAppsView.reset(false /* animate */);
         }
-        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && mSearchImeEnabled
-                && BuildCompat.isAtLeastR()) {
-            mInsetController.onAnimationEnd(mProgress);
-            if (Float.compare(mProgress, 0f) == 0) {
-                EditText editText = mAppsView.getSearchUiManager().getEditText();
-                if (editText != null && !mInsetController.showSearchEduIfNecessary()) {
-                    editText.requestFocus();
-                }
-            }
-            // TODO: should make the controller hide synchronously
-        }
-    }
-
-    @Override
-    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
-        if (s.equals(PREF_KEY_SHOW_SEARCH_IME)) {
-            mSearchImeEnabled = sharedPreferences.getBoolean(s, true);
-        }
     }
 }
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index 39410a7..0d42950 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -20,10 +20,10 @@
 import android.graphics.Rect;
 import android.view.KeyEvent;
 import android.view.animation.Interpolator;
-import android.widget.EditText;
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.anim.PropertySetter;
 
 /**
@@ -75,7 +75,7 @@
      * @return the edit text object
      */
     @Nullable
-    EditText getEditText();
+    ExtendedEditText getEditText();
 
     /**
      * sets highlight result's title
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 426fd0c..2261d51 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -32,7 +32,6 @@
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.view.animation.Interpolator;
-import android.widget.EditText;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
@@ -230,7 +229,7 @@
     }
 
     @Override
-    public EditText getEditText() {
+    public ExtendedEditText getEditText() {
         return this;
     }
 }
diff --git a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
index a79ec43..fdacd3d 100644
--- a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
@@ -51,10 +51,17 @@
             ViewGroup parent, int viewType);
 
     /**
+     * Returns supported item per row combinations supported
+     */
+    public int[] getSupportedItemsPerRowArray() {
+        return new int[]{};
+    }
+
+    /**
      * Returns how many cells a view should span
      */
-    public int getGridSpanSize(int viewType, int appsPerRow) {
-        return appsPerRow * AllAppsGridAdapter.SPAN_MULTIPLIER;
+    public int getItemsPerRow(int viewType, int appsPerRow) {
+        return appsPerRow;
     }
 
     /**
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 8016b2d..6b9ed09 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -16,9 +16,6 @@
 
 package com.android.launcher3.anim;
 
-import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
-
-import android.content.Context;
 import android.graphics.Path;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.AccelerateInterpolator;
@@ -67,9 +64,6 @@
      */
     public static final Interpolator FINAL_FRAME = t -> t < 1 ? 0 : 1;
 
-    private static final int MIN_SETTLE_DURATION = 200;
-    private static final float OVERSHOOT_FACTOR = 0.9f;
-
     static {
         Path exaggeratedEase = new Path();
         exaggeratedEase.moveTo(0, 0);
@@ -175,76 +169,4 @@
             float upperBound) {
         return t -> Utilities.mapRange(interpolator.getInterpolation(t), lowerBound, upperBound);
     }
-
-    /**
-     * Computes parameters necessary for an overshoot effect.
-     */
-    public static class OvershootParams {
-        public Interpolator interpolator;
-        public float start;
-        public float end;
-        public long duration;
-
-        /**
-         * Given the input params, sets OvershootParams variables to be used by the caller.
-         * @param startProgress The progress from 0 to 1 that the overshoot starts from.
-         * @param overshootPastProgress The progress from 0 to 1 where we overshoot past (should
-         *        either be equal to startProgress or endProgress, depending on if we want to
-         *        overshoot immediately or only once we reach the end).
-         * @param endProgress The final progress from 0 to 1 that we will settle to.
-         * @param velocityPxPerMs The initial velocity that causes this overshoot.
-         * @param totalDistancePx The distance against which progress is calculated.
-         */
-        public OvershootParams(float startProgress, float overshootPastProgress,
-                float endProgress, float velocityPxPerMs, int totalDistancePx, Context context) {
-            velocityPxPerMs = Math.abs(velocityPxPerMs);
-            overshootPastProgress = Math.max(overshootPastProgress, startProgress);
-            start = startProgress;
-            int startPx = (int) (start * totalDistancePx);
-            // Overshoot by about half a frame.
-            float overshootBy = OVERSHOOT_FACTOR * velocityPxPerMs *
-                    getSingleFrameMs(context) / totalDistancePx / 2;
-            overshootBy = Utilities.boundToRange(overshootBy, 0.02f, 0.15f);
-            end = overshootPastProgress + overshootBy;
-            int endPx = (int) (end  * totalDistancePx);
-            int overshootDistance = endPx - startPx;
-            // Calculate deceleration necessary to reach overshoot distance.
-            // Formula: velocityFinal^2 = velocityInitial^2 + 2 * acceleration * distance
-            //          0 = v^2 + 2ad (velocityFinal == 0)
-            //          a = v^2 / -2d
-            float decelerationPxPerMs = velocityPxPerMs * velocityPxPerMs / (2 * overshootDistance);
-            // Calculate time necessary to reach peak of overshoot.
-            // Formula: acceleration = velocity / time
-            //          time = velocity / acceleration
-            duration = (long) (velocityPxPerMs / decelerationPxPerMs);
-
-            // Now that we're at the top of the overshoot, need to settle back to endProgress.
-            float settleDistance = end - endProgress;
-            int settleDistancePx = (int) (settleDistance * totalDistancePx);
-            // Calculate time necessary for the settle.
-            // Formula: distance = velocityInitial * time + 1/2 * acceleration * time^2
-            //          d = 1/2at^2 (velocityInitial = 0, since we just stopped at the top)
-            //          t = sqrt(2d/a)
-            // Above formula assumes constant acceleration. Since we use ACCEL_DEACCEL, we actually
-            // have acceleration to halfway then deceleration the rest. So the formula becomes:
-            //          t = sqrt(d/a) * 2 (half the distance for accel, half for deaccel)
-            long settleDuration = (long) Math.sqrt(settleDistancePx / decelerationPxPerMs) * 4;
-
-            settleDuration = Math.max(MIN_SETTLE_DURATION, settleDuration);
-            // How much of the animation to devote to playing the overshoot (the rest is for settle).
-            float overshootFraction = (float) duration / (duration + settleDuration);
-            duration += settleDuration;
-            // Finally, create the interpolator, composed of two interpolators: an overshoot, which
-            // reaches end > 1, and then a settle to endProgress.
-            Interpolator overshoot = Interpolators.clampToProgress(DEACCEL, 0, overshootFraction);
-            // The settle starts at 1, where 1 is the top of the overshoot, and maps to a fraction
-            // such that final progress is endProgress. For example, if we overshot to 1.1 but want
-            // to end at 1, we need to map to 1/1.1.
-            Interpolator settle = Interpolators.clampToProgress(Interpolators.mapToProgress(
-                    ACCEL_DEACCEL, 1, (endProgress - start) / (end - start)), overshootFraction, 1);
-            interpolator = t -> t <= overshootFraction
-                    ? overshoot.getInterpolation(t)
-                    : settle.getInterpolation(t);
-        }
-    }
 }
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 5dc94ff..c972cbb 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -58,6 +58,7 @@
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.WidgetCell;
 import com.android.launcher3.widget.WidgetHostViewLoader;
 import com.android.launcher3.widget.WidgetImageView;
 import com.android.launcher3.widget.WidgetManagerHelper;
@@ -78,7 +79,7 @@
     private LauncherAppState mApp;
     private InvariantDeviceProfile mIdp;
 
-    private LivePreviewWidgetCell mWidgetCell;
+    private WidgetCell mWidgetCell;
 
     // Widget request specific options.
     private LauncherAppWidgetHost mAppWidgetHost;
@@ -117,8 +118,9 @@
             }
         }
 
-        mWidgetCell.setOnTouchListener(this);
-        mWidgetCell.setOnLongClickListener(this);
+        WidgetImageView preview = mWidgetCell.findViewById(R.id.widget_preview);
+        preview.setOnTouchListener(this);
+        preview.setOnLongClickListener(this);
 
         // savedInstanceState is null when the activity is created the first time (i.e., avoids
         // duplicate logging during rotation)
diff --git a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
deleted file mode 100644
index fa062f5..0000000
--- a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
+++ /dev/null
@@ -1,123 +0,0 @@
-package com.android.launcher3.dragndrop;
-
-import static com.android.launcher3.Utilities.ATLEAST_S;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.RemoteViews;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.icons.BitmapRenderer;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.widget.WidgetCell;
-
-/**
- * Extension of {@link WidgetCell} which supports generating previews from {@link RemoteViews}
- */
-public class LivePreviewWidgetCell extends WidgetCell {
-
-    private RemoteViews mPreview;
-
-    public LivePreviewWidgetCell(Context context) {
-        this(context, null);
-    }
-
-    public LivePreviewWidgetCell(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public LivePreviewWidgetCell(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    public void setPreview(RemoteViews view) {
-        mPreview = view;
-    }
-
-    public RemoteViews getPreview() {
-        return mPreview;
-    }
-
-    /** Resets any resource. This should be called before recycling this view. */
-    public void reset() {
-        mPreview = null;
-    }
-
-    @Override
-    public void ensurePreview() {
-        if (mPreview != null && mActiveRequest == null) {
-            Bitmap preview = generateFromRemoteViews(
-                    mActivity, mPreview, mItem.widgetInfo, mPresetPreviewSize, new int[1]);
-            if (preview != null) {
-                applyPreview(preview);
-                return;
-            }
-        }
-        super.ensurePreview();
-    }
-
-    @Override
-    public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) {
-        if (ATLEAST_S
-                && mPreview == null
-                && item.widgetInfo != null
-                && item.widgetInfo.previewLayout != Resources.ID_NULL) {
-            mPreview = new RemoteViews(item.widgetInfo.provider.getPackageName(),
-                    item.widgetInfo.previewLayout);
-        }
-
-        super.applyFromCellItem(item, loader);
-    }
-
-    /**
-     * Generates a bitmap by inflating {@param views}.
-     * @see com.android.launcher3.WidgetPreviewLoader#generateWidgetPreview
-     *
-     * TODO: Consider moving this to the background thread.
-     */
-    public static Bitmap generateFromRemoteViews(BaseActivity activity, RemoteViews views,
-            LauncherAppWidgetProviderInfo info, int previewSize, int[] preScaledWidthOut) {
-
-        DeviceProfile dp = activity.getDeviceProfile();
-        int viewWidth = dp.cellWidthPx * info.spanX;
-        int viewHeight = dp.cellHeightPx * info.spanY;
-
-        final View v;
-        try {
-            v = views.apply(activity, new FrameLayout(activity));
-            v.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
-
-            viewWidth = v.getMeasuredWidth();
-            viewHeight = v.getMeasuredHeight();
-            v.layout(0, 0, viewWidth, viewHeight);
-        } catch (Exception e) {
-            return null;
-        }
-
-        preScaledWidthOut[0] = viewWidth;
-        final int bitmapWidth, bitmapHeight;
-        final float scale;
-        if (viewWidth > previewSize) {
-            scale = ((float) previewSize) / viewWidth;
-            bitmapWidth = previewSize;
-            bitmapHeight = (int) (viewHeight * scale);
-        } else {
-            scale = 1;
-            bitmapWidth = viewWidth;
-            bitmapHeight = viewHeight;
-        }
-
-        return BitmapRenderer.createSoftwareBitmap(bitmapWidth, bitmapHeight, c -> {
-            c.scale(scale, scale);
-            v.draw(c);
-        });
-    }
-}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index d554bb9..c5ffff9 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
 
 import android.content.Context;
+import android.view.View;
 
 import androidx.annotation.Nullable;
 import androidx.slice.SliceItem;
@@ -407,6 +408,12 @@
 
         @UiEvent(doc = "User switched to AllApps Work tab by tapping on it.")
         LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB(722),
+
+        @UiEvent(doc = "All apps vertical fling started.")
+        LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN(724),
+
+        @UiEvent(doc = "All apps vertical fling ended.")
+        LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END(725)
         ;
 
         // ADD MORE
@@ -525,6 +532,13 @@
          */
         default void log(EventEnum event) {
         }
+
+        /**
+         * Builds the final message and logs it to two different atoms, one for
+         * event tracking and the other for jank tracking.
+         */
+        default void sendToInteractionJankMonitor(EventEnum event, View v) {
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index 543ee2a..ad553d5 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -124,7 +124,7 @@
 
     public void bindUpdatedWidgets(BgDataModel dataModel) {
         final ArrayList<WidgetsListBaseEntry> widgets =
-                dataModel.widgetsModel.getWidgetsList(mApp.getContext());
+                dataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext());
         scheduleCallbackTask(c -> c.bindAllWidgets(widgets));
     }
 
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 209c5be..f6c7c06 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -192,6 +192,16 @@
             loadWorkspace(allShortcuts);
             logger.addSplit("loadWorkspace");
 
+            // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
+            // sanitizeData should not be invoked if the workspace is loaded from a db different
+            // from the main db as defined in the invariant device profile.
+            // (e.g. both grid preview and minimal device mode uses a different db)
+            if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
+                verifyNotStopped();
+                sanitizeData();
+                logger.addSplit("sanitizeData");
+            }
+
             verifyNotStopped();
             mResults.bindWorkspace();
             logger.addSplit("bindWorkspace");
@@ -274,14 +284,6 @@
                 loadFolderNames();
             }
 
-            // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
-            // sanitizeData should not be invoked if the workspace is loaded from a db different
-            // from the main db as defined in the invariant device profile.
-            // (e.g. both grid preview and minimal device mode uses a different db)
-            if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
-                sanitizeData();
-            }
-
             verifyNotStopped();
             updateHandler.finish();
             logger.addSplit("finish icon update");
@@ -875,10 +877,11 @@
                     mBgDataModel.itemsIdMap.remove(folderId);
                 }
             }
-            // Remove any ghost widgets
-            LauncherSettings.Settings.call(contentResolver,
-                    LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS);
+
         }
+        // Remove any ghost widgets
+        LauncherSettings.Settings.call(contentResolver,
+                LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS);
 
         // Update pinned state of model shortcuts
         mBgDataModel.updateShortcutPinnedState(context);
diff --git a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
index 434776c..c0dc34a 100644
--- a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.model;
 
-import android.content.Intent;
 import android.os.UserHandle;
 
 import com.android.launcher3.LauncherAppState;
@@ -66,8 +65,7 @@
         final ArrayList<WorkspaceItemInfo> updatedWorkspaceItems = new ArrayList<>();
         synchronized (dataModel) {
             dataModel.forAllWorkspaceItemInfos(mUser, si -> {
-                Intent intent = si.getIntent();
-                if ((intent != null) && mPackageName.equals(intent.getPackage())) {
+                if (mPackageName.equals(si.getTargetPackage())) {
                     si.runtimeStatusFlags &= ~ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
                     si.setProgressLevel(downloadInfo);
                     updatedWorkspaceItems.add(si);
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 1380e9e..9889a80 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.model;
 
-import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 
@@ -72,9 +71,8 @@
         synchronized (dataModel) {
             final HashSet<ItemInfo> updates = new HashSet<>();
             dataModel.forAllWorkspaceItemInfos(mInstallInfo.user, si -> {
-                Intent intent = si.getIntent();
-                if (si.hasPromiseIconUi() && (intent != null)
-                        && mInstallInfo.packageName.equals(intent.getPackage())) {
+                if (si.hasPromiseIconUi()
+                        && mInstallInfo.packageName.equals(si.getTargetPackage())) {
                     int installProgress = mInstallInfo.progress;
 
                     si.setProgressLevel(installProgress, PackageInstallInfo.STATUS_INSTALLING);
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 00ac12f..e54f1e7 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -184,6 +184,24 @@
         return Optional.ofNullable(getIntent()).map(Intent::getComponent).orElse(mComponentName);
     }
 
+    /**
+     * Returns this item's package name.
+     *
+     * Prioritizes the component package name, then uses the intent package name as a fallback.
+     * This ensures deep shortcuts are supported.
+     */
+    @Nullable
+    public String getTargetPackage() {
+        ComponentName component = getTargetComponent();
+        Intent intent = getIntent();
+
+        return component != null
+                ? component.getPackageName()
+                : intent != null
+                        ? intent.getPackage()
+                        : null;
+    }
+
     public void writeToValues(ContentWriter writer) {
         writer.put(LauncherSettings.Favorites.ITEM_TYPE, itemType)
                 .put(LauncherSettings.Favorites.CONTAINER, container)
diff --git a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
index 408796f..6189dc9 100644
--- a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
@@ -268,7 +268,9 @@
         } else {
             lp.leftMargin = lp.rightMargin = 0;
             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
-            lp.bottomMargin = grid.hotseatBarSizePx + insets.bottom;
+            lp.bottomMargin = grid.isTaskbarPresent
+                    ? grid.workspacePadding.bottom + insets.bottom
+                    : grid.hotseatBarSizePx + insets.bottom;
         }
         setLayoutParams(lp);
     }
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 1853632..7780894 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -58,8 +58,11 @@
     private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>();
     /** Maps packages to their DotInfo's . */
     private Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>();
-    /** Maps packages to their Widgets */
+
+    /** All installed widgets. */
     private List<WidgetsListBaseEntry> mAllWidgets = List.of();
+    /** Widgets that can be recommended to the users. */
+    private List<ItemInfo> mRecommendedWidgets = List.of();
 
     private PopupDataChangeListener mChangeListener = PopupDataChangeListener.INSTANCE;
 
@@ -187,6 +190,15 @@
         notificationListener.cancelNotificationFromLauncher(notificationKey);
     }
 
+    /**
+     * Sets a list of recommended widgets ordered by their order of appearance in the widgets
+     * recommendation UI.
+     */
+    public void setRecommendedWidgets(List<ItemInfo> recommendedWidgets) {
+        mRecommendedWidgets = recommendedWidgets;
+        mChangeListener.onRecommendedWidgetsBound();
+    }
+
     public void setAllWidgets(List<WidgetsListBaseEntry> allWidgets) {
         mAllWidgets = allWidgets;
         mChangeListener.onWidgetsBound();
@@ -200,6 +212,21 @@
         return mAllWidgets;
     }
 
+    /** Returns a list of recommended widgets. */
+    public List<WidgetItem> getRecommendedWidgets() {
+        HashMap<ComponentKey, WidgetItem> allWidgetItems = new HashMap<>();
+        mAllWidgets.stream()
+                .filter(entry -> entry instanceof WidgetsListContentEntry)
+                .forEach(entry -> ((WidgetsListContentEntry) entry).mWidgets
+                        .forEach(widget -> allWidgetItems.put(
+                                new ComponentKey(widget.componentName, widget.user), widget)));
+        return mRecommendedWidgets.stream()
+                .map(recommendedWidget -> allWidgetItems.get(
+                        new ComponentKey(recommendedWidget.getTargetComponent(),
+                                recommendedWidget.user)))
+                .collect(Collectors.toList());
+    }
+
     public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
         return mAllWidgets.stream()
                 .filter(row -> row instanceof WidgetsListContentEntry
@@ -244,5 +271,8 @@
         default void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) { }
 
         default void onWidgetsBound() { }
+
+        /** A callback to get notified when recommended widgets are bound. */
+        default void onRecommendedWidgetsBound() { }
     }
 }
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index 459aefe..a191df4 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -32,11 +32,11 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.util.AttributeSet;
+import android.util.SizeF;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -299,7 +299,7 @@
             InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext());
 
             Bundle opts = new Bundle();
-            ArrayList<PointF> sizes = AppWidgetResizeFrame
+            ArrayList<SizeF> sizes = AppWidgetResizeFrame
                     .getWidgetSizes(getContext(), idp.numColumns, 1);
             Rect size = AppWidgetResizeFrame.getMinMaxSizes(sizes, null /* outRect */);
             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, size.left);
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index beb5b68..2b51e97 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -26,14 +26,12 @@
 import android.animation.AnimatorSet;
 import android.os.Handler;
 import android.os.Looper;
-import android.util.Log;
 
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
-import com.android.launcher3.testing.TestProtocol;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -90,7 +88,9 @@
 
     public StateHandler[] getStateHandlers() {
         if (mStateHandlers == null) {
-            mStateHandlers = mActivity.createStateHandlers();
+            ArrayList<StateHandler> handlers = new ArrayList<>();
+            mActivity.collectStateHandlers(handlers);
+            mStateHandlers = handlers.toArray(new StateHandler[handlers.size()]);
         }
         return mStateHandlers;
     }
@@ -302,10 +302,6 @@
     }
 
     private PendingAnimation createAnimationToNewWorkspaceInternal(final STATE_TYPE state) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "createAnimationToNewWorkspaceInternal: "
-                    + state);
-        }
         PendingAnimation builder = new PendingAnimation(mConfig.duration);
         if (mConfig.getAnimComponents() != 0) {
             for (StateHandler handler : getStateHandlers()) {
@@ -328,9 +324,6 @@
 
             @Override
             public void onAnimationSuccess(Animator animator) {
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "onAnimationSuccess: " + state);
-                }
                 onStateTransitionEnd(state);
             }
         };
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 7abb653..8a35cb3 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -30,6 +30,8 @@
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.views.BaseDragLayer;
 
+import java.util.List;
+
 /**
  * Abstract activity with state management
  * @param <STATE_TYPE> Type of state object
@@ -46,7 +48,7 @@
     /**
      * Create handlers to control the property changes for this activity
      */
-    protected abstract StateHandler<STATE_TYPE>[] createStateHandlers();
+    protected abstract void collectStateHandlers(List<StateHandler> out);
 
     /**
      * Returns true if the activity is in the provided state
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 44bcc34..fce8fff 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -93,6 +93,6 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        return super.getVisibleElements(launcher) & ~TASKBAR;
+        return (super.getVisibleElements(launcher) | HOTSEAT_ICONS) & ~TASKBAR;
     }
 }
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
index ec949eb..e4c67ee 100644
--- a/src/com/android/launcher3/states/StateAnimationConfig.java
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -75,6 +75,7 @@
             ANIM_DEPTH,
             ANIM_OVERVIEW_ACTIONS_FADE,
             ANIM_TASKBAR_FADE,
+            ANIM_HOTSEAT_FADE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AnimType {}
@@ -95,8 +96,9 @@
     public static final int ANIM_DEPTH = 14;
     public static final int ANIM_OVERVIEW_ACTIONS_FADE = 15;
     public static final int ANIM_TASKBAR_FADE = 16;
+    public static final int ANIM_HOTSEAT_FADE = 17; // if not set, falls back to ANIM_WORKSPACE_FADE
 
-    private static final int ANIM_TYPES_COUNT = 17;
+    private static final int ANIM_TYPES_COUNT = 18;
 
     protected final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
 
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 218172b..7cb6e34 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -103,7 +103,8 @@
     public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
 
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
-    public static final String OVERIEW_NOT_ALLAPPS = "b/156095088";
     public static final String NO_SWIPE_TO_HOME = "b/158017601";
     public static final String WORK_PROFILE_REMOVED = "b/159671700";
+    public static final String TIS_NO_EVENTS = "b/180915942";
+    public static final String GET_RECENTS_FAILED = "b/177472267";
 }
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index b28d6f7..516fc74 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -38,24 +38,19 @@
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.os.SystemClock;
-import android.util.Log;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 
-import androidx.core.os.BuildCompat;
-
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.FlingBlockCheck;
 import com.android.launcher3.util.TouchController;
 
@@ -202,10 +197,6 @@
 
         mFromState = newFromState;
         mToState = newToState;
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "reinitCurrentAnimation: "
-                    + newToState.ordinal + " " + getClass().getSimpleName());
-        }
 
         mStartProgress = 0;
         mPassedOverviewAtomicThreshold = false;
@@ -269,13 +260,6 @@
         }
         mCanBlockFling = mFromState == NORMAL;
         mFlingBlockCheck.unblockFling();
-        // Must be called after all the animation controllers have been paused
-        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()
-                && BuildCompat.isAtLeastR()
-                && (mToState == ALL_APPS || mToState == NORMAL)) {
-            mLauncher.getAllAppsController().getInsetController().onDragStart(
-                    mFromState == NORMAL ? 1f : 0f);
-        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 919673f..f876dd9 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -57,7 +57,7 @@
         if (!(v.getTag() instanceof ItemInfo)) return false;
 
         launcher.setWaitingForResult(null);
-        beginDrag(v, launcher, (ItemInfo) v.getTag(), new DragOptions());
+        beginDrag(v, launcher, (ItemInfo) v.getTag(), launcher.getDefaultWorkspaceDragOptions());
         return true;
     }
 
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
deleted file mode 100644
index 4f4cccd..0000000
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ /dev/null
@@ -1,553 +0,0 @@
-/*
- * Copyright (C) 2015 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.util;
-
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.launcher3.CellLayout;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.ShortcutAndWidgetContainer;
-import com.android.launcher3.config.FeatureFlags;
-
-import java.util.Arrays;
-
-/**
- * Calculates the next item that a {@link KeyEvent} should change the focus to.
- *<p>
- * Note, this utility class calculates everything regards to icon index and its (x,y) coordinates.
- * Currently supports:
- * <ul>
- *  <li> full matrix of cells that are 1x1
- *  <li> sparse matrix of cells that are 1x1
- *     [ 1][  ][ 2][  ]
- *     [  ][  ][ 3][  ]
- *     [  ][ 4][  ][  ]
- *     [  ][ 5][ 6][ 7]
- * </ul>
- * *<p>
- * For testing, one can use a BT keyboard, or use following adb command.
- * ex. $ adb shell input keyevent 20 // KEYCODE_DPAD_LEFT
- */
-public class FocusLogic {
-
-    private static final String TAG = "FocusLogic";
-    private static final boolean DEBUG = false;
-
-    /** Item and page index related constant used by {@link #handleKeyEvent}. */
-    public static final int NOOP = -1;
-
-    public static final int PREVIOUS_PAGE_RIGHT_COLUMN  = -2;
-    public static final int PREVIOUS_PAGE_FIRST_ITEM    = -3;
-    public static final int PREVIOUS_PAGE_LAST_ITEM     = -4;
-    public static final int PREVIOUS_PAGE_LEFT_COLUMN   = -5;
-
-    public static final int CURRENT_PAGE_FIRST_ITEM     = -6;
-    public static final int CURRENT_PAGE_LAST_ITEM      = -7;
-
-    public static final int NEXT_PAGE_FIRST_ITEM        = -8;
-    public static final int NEXT_PAGE_LEFT_COLUMN       = -9;
-    public static final int NEXT_PAGE_RIGHT_COLUMN      = -10;
-
-    public static final int ALL_APPS_COLUMN = -11;
-
-    // Matrix related constant.
-    public static final int EMPTY = -1;
-    public static final int PIVOT = 100;
-
-    /**
-     * Returns true only if this utility class handles the key code.
-     */
-    public static boolean shouldConsume(int keyCode) {
-        return (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ||
-                keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
-                keyCode == KeyEvent.KEYCODE_MOVE_HOME || keyCode == KeyEvent.KEYCODE_MOVE_END ||
-                keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN);
-    }
-
-    public static int handleKeyEvent(int keyCode, int [][] map, int iconIdx, int pageIndex,
-            int pageCount, boolean isRtl) {
-
-        int cntX = map == null ? -1 : map.length;
-        int cntY = map == null ? -1 : map[0].length;
-
-        if (DEBUG) {
-            Log.v(TAG, String.format(
-                    "handleKeyEvent START: cntX=%d, cntY=%d, iconIdx=%d, pageIdx=%d, pageCnt=%d",
-                    cntX, cntY, iconIdx, pageIndex, pageCount));
-        }
-
-        int newIndex = NOOP;
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_DPAD_LEFT:
-                newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, -1 /*increment*/, isRtl);
-                if (!isRtl && newIndex == NOOP && pageIndex > 0) {
-                    newIndex = PREVIOUS_PAGE_RIGHT_COLUMN;
-                } else if (isRtl && newIndex == NOOP && pageIndex < pageCount - 1) {
-                    newIndex = NEXT_PAGE_RIGHT_COLUMN;
-                }
-                break;
-            case KeyEvent.KEYCODE_DPAD_RIGHT:
-                newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, 1 /*increment*/, isRtl);
-                if (!isRtl && newIndex == NOOP && pageIndex < pageCount - 1) {
-                    newIndex = NEXT_PAGE_LEFT_COLUMN;
-                } else if (isRtl && newIndex == NOOP && pageIndex > 0) {
-                    newIndex = PREVIOUS_PAGE_LEFT_COLUMN;
-                }
-                break;
-            case KeyEvent.KEYCODE_DPAD_DOWN:
-                newIndex = handleDpadVertical(iconIdx, cntX, cntY, map, 1  /*increment*/);
-                break;
-            case KeyEvent.KEYCODE_DPAD_UP:
-                newIndex = handleDpadVertical(iconIdx, cntX, cntY, map, -1  /*increment*/);
-                break;
-            case KeyEvent.KEYCODE_MOVE_HOME:
-                newIndex = handleMoveHome();
-                break;
-            case KeyEvent.KEYCODE_MOVE_END:
-                newIndex = handleMoveEnd();
-                break;
-            case KeyEvent.KEYCODE_PAGE_DOWN:
-                newIndex = handlePageDown(pageIndex, pageCount);
-                break;
-            case KeyEvent.KEYCODE_PAGE_UP:
-                newIndex = handlePageUp(pageIndex);
-                break;
-            default:
-                break;
-        }
-
-        if (DEBUG) {
-            Log.v(TAG, String.format("handleKeyEvent FINISH: index [%d -> %s]",
-                    iconIdx, getStringIndex(newIndex)));
-        }
-        return newIndex;
-    }
-
-    /**
-     * Returns a matrix of size (m x n) that has been initialized with {@link #EMPTY}.
-     *
-     * @param m                 number of columns in the matrix
-     * @param n                 number of rows in the matrix
-     */
-    // TODO: get rid of dynamic matrix creation.
-    private static int[][] createFullMatrix(int m, int n) {
-        int[][] matrix = new int [m][n];
-
-        for (int i=0; i < m;i++) {
-            Arrays.fill(matrix[i], EMPTY);
-        }
-        return matrix;
-    }
-
-    /**
-     * Returns a matrix of size same as the {@link CellLayout} dimension that is initialized with the
-     * index of the child view.
-     */
-    // TODO: get rid of the dynamic matrix creation
-    public static int[][] createSparseMatrix(CellLayout layout) {
-        ShortcutAndWidgetContainer parent = layout.getShortcutsAndWidgets();
-        final int m = layout.getCountX();
-        final int n = layout.getCountY();
-        final boolean invert = parent.invertLayoutHorizontally();
-
-        int[][] matrix = createFullMatrix(m, n);
-
-        // Iterate thru the children.
-        for (int i = 0; i < parent.getChildCount(); i++ ) {
-            View cell = parent.getChildAt(i);
-            if (!cell.isFocusable()) {
-                continue;
-            }
-            int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX;
-            int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY;
-            int x = invert ? (m - cx - 1) : cx;
-            if (x < m && cy < n) { // check if view fits into matrix, else skip
-                matrix[x][cy] = i;
-            }
-        }
-        if (DEBUG) {
-            printMatrix(matrix);
-        }
-        return matrix;
-    }
-
-    /**
-     * Creates a sparse matrix that merges the icon and hotseat view group using the cell layout.
-     * The size of the returning matrix is [icon column count x (icon + hotseat row count)]
-     * in portrait orientation. In landscape, [(icon + hotseat) column count x (icon row count)]
-     */
-    // TODO: get rid of the dynamic matrix creation
-    public static int[][] createSparseMatrixWithHotseat(
-            CellLayout iconLayout, CellLayout hotseatLayout, DeviceProfile dp) {
-
-        ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
-        ViewGroup hotseatParent = hotseatLayout.getShortcutsAndWidgets();
-
-        boolean isHotseatHorizontal = !dp.isVerticalBarLayout();
-
-        int m, n;
-        if (isHotseatHorizontal) {
-            m = hotseatLayout.getCountX();
-            n = iconLayout.getCountY() + hotseatLayout.getCountY();
-        } else {
-            m = iconLayout.getCountX() + hotseatLayout.getCountX();
-            n = hotseatLayout.getCountY();
-        }
-        int[][] matrix = createFullMatrix(m, n);
-        // Iterate through the children of the workspace.
-        for (int i = 0; i < iconParent.getChildCount(); i++) {
-            View cell = iconParent.getChildAt(i);
-            if (!cell.isFocusable()) {
-                continue;
-            }
-            int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX;
-            int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY;
-            matrix[cx][cy] = i;
-        }
-
-        // Iterate thru the children of the hotseat.
-        for (int i = hotseatParent.getChildCount() - 1; i >= 0; i--) {
-            if (isHotseatHorizontal) {
-                int cx = ((CellLayout.LayoutParams)
-                        hotseatParent.getChildAt(i).getLayoutParams()).cellX;
-                matrix[cx][iconLayout.getCountY()] = iconParent.getChildCount() + i;
-            } else {
-                int cy = ((CellLayout.LayoutParams)
-                        hotseatParent.getChildAt(i).getLayoutParams()).cellY;
-                matrix[iconLayout.getCountX()][cy] = iconParent.getChildCount() + i;
-            }
-        }
-        if (DEBUG) {
-            printMatrix(matrix);
-        }
-        return matrix;
-    }
-
-    /**
-     * Creates a sparse matrix that merges the icon of previous/next page and last column of
-     * current page. When left key is triggered on the leftmost column, sparse matrix is created
-     * that combines previous page matrix and an extra column on the right. Likewise, when right
-     * key is triggered on the rightmost column, sparse matrix is created that combines this column
-     * on the 0th column and the next page matrix.
-     *
-     * @param pivotX    x coordinate of the focused item in the current page
-     * @param pivotY    y coordinate of the focused item in the current page
-     */
-    // TODO: get rid of the dynamic matrix creation
-    public static int[][] createSparseMatrixWithPivotColumn(CellLayout iconLayout,
-            int pivotX, int pivotY) {
-
-        ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
-
-        int[][] matrix = createFullMatrix(iconLayout.getCountX() + 1, iconLayout.getCountY());
-
-        // Iterate thru the children of the top parent.
-        for (int i = 0; i < iconParent.getChildCount(); i++) {
-            View cell = iconParent.getChildAt(i);
-            if (!cell.isFocusable()) {
-                continue;
-            }
-            int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX;
-            int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY;
-            if (pivotX < 0) {
-                matrix[cx - pivotX][cy] = i;
-            } else {
-                matrix[cx][cy] = i;
-            }
-        }
-
-        if (pivotX < 0) {
-            matrix[0][pivotY] = PIVOT;
-        } else {
-            matrix[pivotX][pivotY] = PIVOT;
-        }
-        if (DEBUG) {
-            printMatrix(matrix);
-        }
-        return matrix;
-    }
-
-    //
-    // key event handling methods.
-    //
-
-    /**
-     * Calculates icon that has is closest to the horizontal axis in reference to the cur icon.
-     *
-     * Example of the check order for KEYCODE_DPAD_RIGHT:
-     * [  ][  ][13][14][15]
-     * [  ][ 6][ 8][10][12]
-     * [ X][ 1][ 2][ 3][ 4]
-     * [  ][ 5][ 7][ 9][11]
-     */
-    // TODO: add unit tests to verify all permutation.
-    private static int handleDpadHorizontal(int iconIdx, int cntX, int cntY,
-            int[][] matrix, int increment, boolean isRtl) {
-        if(matrix == null) {
-            throw new IllegalStateException("Dpad navigation requires a matrix.");
-        }
-        int newIconIndex = NOOP;
-
-        int xPos = -1;
-        int yPos = -1;
-        // Figure out the location of the icon.
-        for (int i = 0; i < cntX; i++) {
-            for (int j = 0; j < cntY; j++) {
-                if (matrix[i][j] == iconIdx) {
-                    xPos = i;
-                    yPos = j;
-                }
-            }
-        }
-        if (DEBUG) {
-            Log.v(TAG, String.format("\thandleDpadHorizontal: \t[x, y]=[%d, %d] iconIndex=%d",
-                    xPos, yPos, iconIdx));
-        }
-
-        // Rule1: check first in the horizontal direction
-        for (int x = xPos + increment; 0 <= x && x < cntX; x += increment) {
-            if ((newIconIndex = inspectMatrix(x, yPos, cntX, cntY, matrix)) != NOOP
-                    && newIconIndex != ALL_APPS_COLUMN) {
-                return newIconIndex;
-            }
-        }
-
-        // Rule2: check (x1-n, yPos + increment),   (x1-n, yPos - increment)
-        //              (x2-n, yPos + 2*increment), (x2-n, yPos - 2*increment)
-        int nextYPos1;
-        int nextYPos2;
-        boolean haveCrossedAllAppsColumn1 = false;
-        boolean haveCrossedAllAppsColumn2 = false;
-        int x = -1;
-        for (int coeff = 1; coeff < cntY; coeff++) {
-            nextYPos1 = yPos + coeff * increment;
-            nextYPos2 = yPos - coeff * increment;
-            x = xPos + increment * coeff;
-            if (inspectMatrix(x, nextYPos1, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
-                haveCrossedAllAppsColumn1 = true;
-            }
-            if (inspectMatrix(x, nextYPos2, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
-                haveCrossedAllAppsColumn2 = true;
-            }
-            for (; 0 <= x && x < cntX; x += increment) {
-                int offset1 = haveCrossedAllAppsColumn1 && x < cntX - 1 ? increment : 0;
-                newIconIndex = inspectMatrix(x, nextYPos1 + offset1, cntX, cntY, matrix);
-                if (newIconIndex != NOOP) {
-                    return newIconIndex;
-                }
-                int offset2 = haveCrossedAllAppsColumn2 && x < cntX - 1 ? -increment : 0;
-                newIconIndex = inspectMatrix(x, nextYPos2 + offset2, cntX, cntY, matrix);
-                if (newIconIndex != NOOP) {
-                    return newIconIndex;
-                }
-            }
-        }
-
-        // Rule3: if switching between pages, do a brute-force search to find an item that was
-        //        missed by rules 1 and 2 (such as when going from a bottom right icon to top left)
-        if (iconIdx == PIVOT) {
-            if (isRtl) {
-                return increment < 0 ? NEXT_PAGE_FIRST_ITEM : PREVIOUS_PAGE_LAST_ITEM;
-            }
-            return increment < 0 ? PREVIOUS_PAGE_LAST_ITEM : NEXT_PAGE_FIRST_ITEM;
-        }
-        return newIconIndex;
-    }
-
-    /**
-     * Calculates icon that is closest to the vertical axis in reference to the current icon.
-     *
-     * Example of the check order for KEYCODE_DPAD_DOWN:
-     * [  ][  ][  ][ X][  ][  ][  ]
-     * [  ][  ][ 5][ 1][ 4][  ][  ]
-     * [  ][10][ 7][ 2][ 6][ 9][  ]
-     * [14][12][ 9][ 3][ 8][11][13]
-     */
-    // TODO: add unit tests to verify all permutation.
-    private static int handleDpadVertical(int iconIndex, int cntX, int cntY,
-            int [][] matrix, int increment) {
-        int newIconIndex = NOOP;
-        if(matrix == null) {
-            throw new IllegalStateException("Dpad navigation requires a matrix.");
-        }
-
-        int xPos = -1;
-        int yPos = -1;
-        // Figure out the location of the icon.
-        for (int i = 0; i< cntX; i++) {
-            for (int j = 0; j < cntY; j++) {
-                if (matrix[i][j] == iconIndex) {
-                    xPos = i;
-                    yPos = j;
-                }
-            }
-        }
-
-        if (DEBUG) {
-            Log.v(TAG, String.format("\thandleDpadVertical: \t[x, y]=[%d, %d] iconIndex=%d",
-                    xPos, yPos, iconIndex));
-        }
-
-        // Rule1: check first in the dpad direction
-        for (int y = yPos + increment; 0 <= y && y <cntY && 0 <= y; y += increment) {
-            if ((newIconIndex = inspectMatrix(xPos, y, cntX, cntY, matrix)) != NOOP
-                    && newIconIndex != ALL_APPS_COLUMN) {
-                return newIconIndex;
-            }
-        }
-
-        // Rule2: check (xPos + increment, y_(1-n)),   (xPos - increment, y_(1-n))
-        //              (xPos + 2*increment, y_(2-n))), (xPos - 2*increment, y_(2-n))
-        int nextXPos1;
-        int nextXPos2;
-        boolean haveCrossedAllAppsColumn1 = false;
-        boolean haveCrossedAllAppsColumn2 = false;
-        int y = -1;
-        for (int coeff = 1; coeff < cntX; coeff++) {
-            nextXPos1 = xPos + coeff * increment;
-            nextXPos2 = xPos - coeff * increment;
-            y = yPos + increment * coeff;
-            if (inspectMatrix(nextXPos1, y, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
-                haveCrossedAllAppsColumn1 = true;
-            }
-            if (inspectMatrix(nextXPos2, y, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
-                haveCrossedAllAppsColumn2 = true;
-            }
-            for (; 0 <= y && y < cntY; y = y + increment) {
-                int offset1 = haveCrossedAllAppsColumn1 && y < cntY - 1 ? increment : 0;
-                newIconIndex = inspectMatrix(nextXPos1 + offset1, y, cntX, cntY, matrix);
-                if (newIconIndex != NOOP) {
-                    return newIconIndex;
-                }
-                int offset2 = haveCrossedAllAppsColumn2 && y < cntY - 1 ? -increment : 0;
-                newIconIndex = inspectMatrix(nextXPos2 + offset2, y, cntX, cntY, matrix);
-                if (newIconIndex != NOOP) {
-                    return newIconIndex;
-                }
-            }
-        }
-        return newIconIndex;
-    }
-
-    private static int handleMoveHome() {
-        return CURRENT_PAGE_FIRST_ITEM;
-    }
-
-    private static int handleMoveEnd() {
-        return CURRENT_PAGE_LAST_ITEM;
-    }
-
-    private static int handlePageDown(int pageIndex, int pageCount) {
-        if (pageIndex < pageCount -1) {
-            return NEXT_PAGE_FIRST_ITEM;
-        }
-        return CURRENT_PAGE_LAST_ITEM;
-    }
-
-    private static int handlePageUp(int pageIndex) {
-        if (pageIndex > 0) {
-            return PREVIOUS_PAGE_FIRST_ITEM;
-        } else {
-            return CURRENT_PAGE_FIRST_ITEM;
-        }
-    }
-
-    //
-    // Helper methods.
-    //
-
-    private static boolean isValid(int xPos, int yPos, int countX, int countY) {
-        return (0 <= xPos && xPos < countX && 0 <= yPos && yPos < countY);
-    }
-
-    private static int inspectMatrix(int x, int y, int cntX, int cntY, int[][] matrix) {
-        int newIconIndex = NOOP;
-        if (isValid(x, y, cntX, cntY)) {
-            if (matrix[x][y] != -1) {
-                newIconIndex = matrix[x][y];
-                if (DEBUG) {
-                    Log.v(TAG, String.format("\t\tinspect: \t[x, y]=[%d, %d] %d",
-                            x, y, matrix[x][y]));
-                }
-                return newIconIndex;
-            }
-        }
-        return newIconIndex;
-    }
-
-    /**
-     * Only used for debugging.
-     */
-    private static String getStringIndex(int index) {
-        switch(index) {
-            case NOOP: return "NOOP";
-            case PREVIOUS_PAGE_FIRST_ITEM:  return "PREVIOUS_PAGE_FIRST";
-            case PREVIOUS_PAGE_LAST_ITEM:   return "PREVIOUS_PAGE_LAST";
-            case PREVIOUS_PAGE_RIGHT_COLUMN:return "PREVIOUS_PAGE_RIGHT_COLUMN";
-            case CURRENT_PAGE_FIRST_ITEM:   return "CURRENT_PAGE_FIRST";
-            case CURRENT_PAGE_LAST_ITEM:    return "CURRENT_PAGE_LAST";
-            case NEXT_PAGE_FIRST_ITEM:      return "NEXT_PAGE_FIRST";
-            case NEXT_PAGE_LEFT_COLUMN:     return "NEXT_PAGE_LEFT_COLUMN";
-            case ALL_APPS_COLUMN:           return "ALL_APPS_COLUMN";
-            default:
-                return Integer.toString(index);
-        }
-    }
-
-    /**
-     * Only used for debugging.
-     */
-    private static void printMatrix(int[][] matrix) {
-        Log.v(TAG, "\tprintMap:");
-        int m = matrix.length;
-        int n = matrix[0].length;
-
-        for (int j=0; j < n; j++) {
-            String colY = "\t\t";
-            for (int i=0; i < m; i++) {
-                colY +=  String.format("%3d",matrix[i][j]);
-            }
-            Log.v(TAG, colY);
-        }
-    }
-
-    /**
-     * @param edgeColumn the column of the new icon. either {@link #NEXT_PAGE_LEFT_COLUMN} or
-     * {@link #NEXT_PAGE_RIGHT_COLUMN}
-     * @return the view adjacent to {@param oldView} in the {@param nextPage} of the folder.
-     */
-    public static View getAdjacentChildInNextFolderPage(
-            ShortcutAndWidgetContainer nextPage, View oldView, int edgeColumn) {
-        final int newRow = ((CellLayout.LayoutParams) oldView.getLayoutParams()).cellY;
-
-        int column = (edgeColumn == NEXT_PAGE_LEFT_COLUMN) ^ nextPage.invertLayoutHorizontally()
-                ? 0 : (((CellLayout) nextPage.getParent()).getCountX() - 1);
-
-        for (; column >= 0; column--) {
-            for (int row = newRow; row >= 0; row--) {
-                View newView = nextPage.getChildAt(column, row);
-                if (newView != null) {
-                    return newView;
-                }
-            }
-        }
-        return null;
-    }
-}
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 15566a4..03c58bb 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -30,7 +30,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
@@ -86,6 +85,8 @@
 
         if (v instanceof WidgetCell) {
             return beginDraggingWidget((WidgetCell) v);
+        } else if (v.getParent() instanceof WidgetCell) {
+            return beginDraggingWidget((WidgetCell) v.getParent());
         }
         return true;
     }
@@ -101,9 +102,7 @@
         }
 
         PendingItemDragHelper dragHelper = new PendingItemDragHelper(v);
-        if (v instanceof LivePreviewWidgetCell) {
-            dragHelper.setPreview(((LivePreviewWidgetCell) v).getPreview());
-        }
+        dragHelper.setPreview(v.getPreview());
 
         int[] loc = new int[2];
         getPopupContainer().getLocationInDragLayer(image, loc);
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 54e51b2..3285c18 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -16,12 +16,9 @@
 
 package com.android.launcher3.widget;
 
-import static com.android.launcher3.Utilities.ATLEAST_S;
-
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.graphics.PointF;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.SparseBooleanArray;
@@ -219,16 +216,6 @@
     }
 
     @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        super.onSizeChanged(w, h, oldw, oldh);
-
-        if (ATLEAST_S) {
-            float density = getContext().getResources().getDisplayMetrics().density;
-            setCurrentSize(new PointF(w / density, h / density));
-        }
-    }
-
-    @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         info.setClassName(getClass().getName());
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 8c3206d..4b113d8 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -22,7 +22,6 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -30,6 +29,7 @@
 import android.text.Layout;
 import android.text.StaticLayout;
 import android.text.TextPaint;
+import android.util.SizeF;
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
 import android.view.View;
@@ -112,7 +112,7 @@
     }
 
     @Override
-    public void updateAppWidgetSize(Bundle newOptions, List<PointF> sizes) {
+    public void updateAppWidgetSize(Bundle newOptions, List<SizeF> sizes) {
         // No-op
     }
 
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 8fe42f4..6e83836 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -35,7 +35,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DraggableView;
-import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.icons.LauncherIcons;
 
@@ -92,7 +91,7 @@
             int[] previewSizeBeforeScale = new int[1];
 
             if (mPreview != null) {
-                preview = LivePreviewWidgetCell.generateFromRemoteViews(launcher, mPreview,
+                preview = WidgetCell.generateFromRemoteViews(launcher, mPreview,
                         createWidgetInfo.info, maxWidth, previewSizeBeforeScale);
             }
             if (preview == null) {
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 8c315fd..229df50 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -18,7 +18,9 @@
 
 import static com.android.launcher3.Utilities.ATLEAST_S;
 
+import android.appwidget.AppWidgetHostView;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.os.CancellationSignal;
 import android.util.AttributeSet;
@@ -28,7 +30,9 @@
 import android.view.View.OnLayoutChangeListener;
 import android.view.ViewPropertyAnimator;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
 import android.widget.LinearLayout;
+import android.widget.RemoteViews;
 import android.widget.TextView;
 
 import com.android.launcher3.BaseActivity;
@@ -37,6 +41,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.icons.BaseIconFactory;
+import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.model.WidgetItem;
 
 /**
@@ -61,8 +66,8 @@
     /** Widget preview width is calculated by multiplying this factor to the widget cell width. */
     private static final float PREVIEW_SCALE = 0.8f;
 
-    private int mPreviewWidth;
-    private int mPreviewHeight;
+    protected int mPreviewWidth;
+    protected int mPreviewHeight;
     protected int mPresetPreviewSize;
     private int mCellSize;
 
@@ -85,6 +90,9 @@
     protected final DeviceProfile mDeviceProfile;
     private final CheckLongPressHelper mLongPressHelper;
 
+    private RemoteViews mPreview;
+    private AppWidgetHostView mPreviewAppWidgetHostView;
+
     public WidgetCell(Context context) {
         this(context, null);
     }
@@ -123,6 +131,14 @@
         mWidgetDescription = findViewById(R.id.widget_description);
     }
 
+    public void setPreview(RemoteViews view) {
+        mPreview = view;
+    }
+
+    public RemoteViews getPreview() {
+        return mPreview;
+    }
+
     /**
      * Called to clear the view and free attached resources. (e.g., {@link Bitmap}
      */
@@ -142,9 +158,13 @@
             mActiveRequest.cancel();
             mActiveRequest = null;
         }
+        mPreview = null;
+        mPreviewAppWidgetHostView = null;
     }
 
     public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) {
+        applyPreviewLayout(item);
+
         mItem = item;
         mWidgetName.setText(mItem.label);
         mWidgetDims.setText(getContext().getString(R.string.widget_dims_format,
@@ -169,6 +189,27 @@
         }
     }
 
+    private void applyPreviewLayout(WidgetItem item) {
+        if (ATLEAST_S
+                && mPreview == null
+                && item.widgetInfo != null
+                && item.widgetInfo.previewLayout != Resources.ID_NULL) {
+            mPreviewAppWidgetHostView = new AppWidgetHostView(getContext());
+            LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
+                    LauncherAppWidgetProviderInfo.fromProviderInfo(getContext(),
+                            item.widgetInfo.clone());
+            // A hack to force the initial layout to be the preview layout since there is no API for
+            // rendering a preview layout for work profile apps yet. For non-work profile layout, a
+            // proper solution is to use RemoteViews(PackageName, LayoutId).
+            launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout;
+            mPreviewAppWidgetHostView.setAppWidget(/* appWidgetId= */ -1,
+                    launcherAppWidgetProviderInfo);
+            mPreviewAppWidgetHostView.setPadding(/* left= */ 0, /* top= */0, /* right= */
+                    0, /* bottom= */ 0);
+            mPreviewAppWidgetHostView.updateAppWidget(/* remoteViews= */ null);
+        }
+    }
+
     public WidgetImageView getWidgetView() {
         return mWidgetImage;
     }
@@ -217,6 +258,23 @@
     }
 
     public void ensurePreview() {
+        if (mPreview != null && mActiveRequest == null) {
+            Bitmap preview = generateFromRemoteViews(
+                    mActivity, mPreview, mItem.widgetInfo, mPresetPreviewSize, new int[1]);
+            if (preview != null) {
+                applyPreview(preview);
+                return;
+            }
+        }
+
+        if (mPreviewAppWidgetHostView != null) {
+            Bitmap preview = generateFromView(mActivity, mPreviewAppWidgetHostView,
+                    mItem.widgetInfo, mPreviewWidth, new int[1]);
+            if (preview != null) {
+                applyPreview(preview);
+                return;
+            }
+        }
         if (mActiveRequest != null) {
             return;
         }
@@ -273,4 +331,53 @@
         super.onInitializeAccessibilityNodeInfo(info);
         info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
     }
+
+    /**
+     * Generates a bitmap by inflating {@param views}.
+     * @see com.android.launcher3.WidgetPreviewLoader#generateWidgetPreview
+     *
+     * TODO: Consider moving this to the background thread.
+     */
+    public static Bitmap generateFromRemoteViews(BaseActivity activity, RemoteViews views,
+            LauncherAppWidgetProviderInfo info, int previewSize, int[] preScaledWidthOut) {
+        try {
+            return generateFromView(activity, views.apply(activity, new FrameLayout(activity)),
+                    info, previewSize, preScaledWidthOut);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private static Bitmap generateFromView(BaseActivity activity, View v,
+            LauncherAppWidgetProviderInfo info, int previewSize, int[] preScaledWidthOut) {
+
+        DeviceProfile dp = activity.getDeviceProfile();
+        int viewWidth = dp.cellWidthPx * info.spanX;
+        int viewHeight = dp.cellHeightPx * info.spanY;
+
+        v.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
+
+        viewWidth = v.getMeasuredWidth();
+        viewHeight = v.getMeasuredHeight();
+        v.layout(0, 0, viewWidth, viewHeight);
+
+        preScaledWidthOut[0] = viewWidth;
+        final int bitmapWidth, bitmapHeight;
+        final float scale;
+        if (viewWidth > previewSize) {
+            scale = ((float) previewSize) / viewWidth;
+            bitmapWidth = previewSize;
+            bitmapHeight = (int) (viewHeight * scale);
+        } else {
+            scale = 1;
+            bitmapWidth = viewWidth;
+            bitmapHeight = viewHeight;
+        }
+
+        return BitmapRenderer.createSoftwareBitmap(bitmapWidth, bitmapHeight, c -> {
+            c.scale(scale, scale);
+            v.draw(c);
+        });
+    }
 }
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index 31cbff6..12e0d43 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -3,11 +3,11 @@
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
 import android.content.Context;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
 import android.util.Log;
+import android.util.SizeF;
 import android.view.View;
 
 import com.android.launcher3.AppWidgetResizeFrame;
@@ -19,6 +19,7 @@
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
+import java.util.stream.Collectors;
 
 public class WidgetHostViewLoader implements DragController.DragListener {
     private static final String TAG = "WidgetHostViewLoader";
@@ -154,7 +155,7 @@
     }
 
     public static Bundle getDefaultOptionsForWidget(Context context, PendingAddWidgetInfo info) {
-        ArrayList<PointF> sizes = AppWidgetResizeFrame
+        ArrayList<SizeF> sizes = AppWidgetResizeFrame
                 .getWidgetSizes(context, info.spanX, info.spanY);
 
         Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context,
@@ -163,19 +164,19 @@
         float xPaddingDips = (padding.left + padding.right) / density;
         float yPaddingDips = (padding.top + padding.bottom) / density;
 
-        for (PointF size : sizes) {
-            size.x = Math.max(0.f, size.x - xPaddingDips);
-            size.y = Math.max(0.f, size.y - yPaddingDips);
-        }
+        ArrayList<SizeF> paddedSizes = sizes.stream().map(
+                size -> new SizeF(Math.max(0.f, size.getWidth() - xPaddingDips),
+                        Math.max(0.f, size.getHeight() - yPaddingDips))).collect(
+                Collectors.toCollection(ArrayList::new));
 
-        Rect rect = AppWidgetResizeFrame.getMinMaxSizes(sizes, null /* outRect */);
+        Rect rect = AppWidgetResizeFrame.getMinMaxSizes(paddedSizes, null /* outRect */);
 
         Bundle options = new Bundle();
         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, rect.left);
         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, rect.top);
         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, rect.right);
         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, rect.bottom);
-        options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, sizes);
+        options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, paddedSizes);
         return options;
     }
 }
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 6abbf21..e6d54a9 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -24,6 +24,7 @@
 import android.util.AttributeSet;
 import android.util.IntProperty;
 import android.util.Pair;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -38,7 +39,6 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.PackageUserKey;
@@ -140,6 +140,7 @@
 
         WidgetsTableUtils.groupWidgetItemsIntoTable(widgets, mMaxHorizontalSpan).forEach(row -> {
             TableRow tableRow = new TableRow(getContext());
+            tableRow.setGravity(Gravity.CENTER_VERTICAL);
             row.forEach(widgetItem -> {
                 WidgetCell widget = addItemCell(tableRow);
                 widget.setPreviewSize(widgetItem.spanX, widgetItem.spanY);
@@ -153,11 +154,12 @@
     }
 
     protected WidgetCell addItemCell(ViewGroup parent) {
-        LivePreviewWidgetCell widget = (LivePreviewWidgetCell) LayoutInflater.from(
-                getContext()).inflate(R.layout.live_preview_widget_cell, parent, false);
+        WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext())
+                .inflate(R.layout.widget_cell, parent, false);
 
-        widget.setOnClickListener(this);
-        widget.setOnLongClickListener(this);
+        WidgetImageView preview = widget.findViewById(R.id.widget_preview);
+        preview.setOnClickListener(this);
+        preview.setOnLongClickListener(this);
         widget.setAnimatePreview(false);
 
         parent.addView(widget);
diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
index a5ed20a..95fa05f 100644
--- a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
+++ b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
@@ -16,10 +16,10 @@
 package com.android.launcher3.widget.picker;
 
 import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.RelativeLayout;
 
 import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.widget.picker.WidgetsFullSheet.SearchAndRecommendationViewHolder;
@@ -33,20 +33,21 @@
         RecyclerViewFastScroller.OnFastScrollChangeListener {
     private final boolean mHasWorkProfile;
     private final SearchAndRecommendationViewHolder mViewHolder;
-    private final RecyclerView mPrimaryRecyclerView;
+    private final WidgetsRecyclerView mPrimaryRecyclerView;
 
     // The following are only non null if mHasWorkProfile is true.
-    @Nullable private final RecyclerView mWorkRecyclerView;
+    @Nullable private final WidgetsRecyclerView mWorkRecyclerView;
     @Nullable private final View mPrimaryWorkTabsView;
     @Nullable private final PersonalWorkPagedView mPrimaryWorkViewPager;
 
+    private WidgetsRecyclerView mCurrentRecyclerView;
     private int mMaxCollapsibleHeight = 0;
 
     SearchAndRecommendationsScrollController(
             boolean hasWorkProfile,
             SearchAndRecommendationViewHolder viewHolder,
-            RecyclerView primaryRecyclerView,
-            @Nullable RecyclerView workRecyclerView,
+            WidgetsRecyclerView primaryRecyclerView,
+            @Nullable WidgetsRecyclerView workRecyclerView,
             @Nullable View personalWorkTabsView,
             @Nullable PersonalWorkPagedView primaryWorkViewPager) {
         mHasWorkProfile = hasWorkProfile;
@@ -55,6 +56,12 @@
         mWorkRecyclerView = workRecyclerView;
         mPrimaryWorkTabsView = personalWorkTabsView;
         mPrimaryWorkViewPager = primaryWorkViewPager;
+        mCurrentRecyclerView = mPrimaryRecyclerView;
+    }
+
+    /** Sets the current active {@link WidgetsRecyclerView}. */
+    public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) {
+        mCurrentRecyclerView = currentRecyclerView;
     }
 
     /**
@@ -64,10 +71,10 @@
         // The maximum vertical distance, in pixels, until the last collapsible element is not
         // visible from the screen when the user scrolls down the recycler view.
         mMaxCollapsibleHeight = mViewHolder.mContainer.getPaddingTop()
-                + mViewHolder.mCollapseHandle.getMeasuredHeight()
-                + mViewHolder.mHeaderTitle.getMeasuredHeight();
+                + measureHeightWithVerticalMargins(mViewHolder.mCollapseHandle)
+                + measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle);
 
-        int topContainerHeight = mViewHolder.mContainer.getMeasuredHeight();
+        int topContainerHeight = measureHeightWithVerticalMargins(mViewHolder.mContainer);
         if (mHasWorkProfile) {
             // In a work profile setup, the full widget sheet contains the following views:
             //           -------               -|
@@ -114,8 +121,8 @@
             //
             // When the views are first inflated, the sum of topOffsetAfterAllViewsCollapsed and
             // mMaxCollapsibleDistance should equal to the top container height.
-            int tabsViewActualHeight =
-                    mPrimaryWorkTabsView.getMeasuredHeight() - mPrimaryWorkTabsView.getPaddingTop();
+            int tabsViewActualHeight = measureHeightWithVerticalMargins(mPrimaryWorkTabsView)
+                    - mPrimaryWorkTabsView.getPaddingTop();
             int topOffsetAfterAllViewsCollapsed =
                     topContainerHeight + tabsViewActualHeight - mMaxCollapsibleHeight;
 
@@ -149,9 +156,12 @@
      * views (e.g. recycler views, tabs) upon scrolling.
      */
     @Override
-    public void onThumbOffsetYChanged(int y) {
+    public void onThumbOffsetYChanged(int unused) {
+        // Always use the recycler view offset because fast scroller offset has a different scale.
+        int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY();
+        if (recyclerViewYOffset < 0) return;
         if (mMaxCollapsibleHeight > 0) {
-            int yDisplacement = Math.max(-y, -mMaxCollapsibleHeight);
+            int yDisplacement = Math.max(-recyclerViewYOffset, -mMaxCollapsibleHeight);
             mViewHolder.mHeaderTitle.setTranslationY(yDisplacement);
             mViewHolder.mSearchBar.setTranslationY(yDisplacement);
             if (mHasWorkProfile) {
@@ -159,4 +169,20 @@
             }
         }
     }
+
+    /** Resets any previous view translation. */
+    public void reset() {
+        mViewHolder.mHeaderTitle.setTranslationY(0);
+        mViewHolder.mSearchBar.setTranslationY(0);
+        if (mHasWorkProfile) {
+            mPrimaryWorkTabsView.setTranslationY(0);
+        }
+    }
+
+    /** private the height, in pixel, + the vertical margins of a given view. */
+    private static int measureHeightWithVerticalMargins(View view) {
+        MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
+        return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
+                + marginLayoutParams.topMargin;
+    }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 91b79f9..330175f 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -82,6 +82,7 @@
     @Nullable private PersonalWorkPagedView mViewPager;
     private int mInitialTabsHeight = 0;
     private View mTabsView;
+    private TextView mNoWidgetsView;
     private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
     private SearchAndRecommendationsScrollController mSearchAndRecommendationsScrollController;
 
@@ -110,9 +111,6 @@
         RecyclerViewFastScroller fastScroller = findViewById(R.id.fast_scroller);
         if (mHasWorkProfile) {
             mViewPager = findViewById(R.id.widgets_view_pager);
-            // Temporarily disable swipe gesture until widgets list horizontal scrollviews per
-            // app are replaced by gird views.
-            mViewPager.setSwipeGestureEnabled(false);
             mViewPager.initParentViews(this);
             mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
             mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.PRIMARY);
@@ -144,21 +142,38 @@
                 mViewPager);
         fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController);
 
+        mNoWidgetsView = findViewById(R.id.no_widgets_text);
+
         onWidgetsBound();
     }
 
     @Override
     public void onActivePageChanged(int currentActivePage) {
-        mAdapters.get(currentActivePage).mWidgetsRecyclerView.bindFastScrollbar();
+        AdapterHolder currentAdapterHolder = mAdapters.get(currentActivePage);
+        WidgetsRecyclerView currentRecyclerView = currentAdapterHolder.mWidgetsRecyclerView;
+        currentRecyclerView.bindFastScrollbar();
+        mSearchAndRecommendationsScrollController.setCurrentRecyclerView(currentRecyclerView);
+
+        updateNoWidgetsView(currentAdapterHolder);
 
         reset();
     }
 
+    private void updateNoWidgetsView(AdapterHolder adapterHolder) {
+        boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.getItemCount() > 0;
+        adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
+
+        // Always resets the text in case this is updated by search.
+        mNoWidgetsView.setText(R.string.no_widgets_available);
+        mNoWidgetsView.setVisibility(isWidgetAvailable ? GONE : VISIBLE);
+    }
+
     private void reset() {
         mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop();
         if (mHasWorkProfile) {
             mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
         }
+        mSearchAndRecommendationsScrollController.reset();
     }
 
     @VisibleForTesting
@@ -241,6 +256,12 @@
             mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
                     maxSpansPerRow);
         }
+
+        if (mInitialTabsHeight == 0 && mTabsView != null) {
+            mInitialTabsHeight = measureHeightWithVerticalMargins(mTabsView);
+        }
+
+        mSearchAndRecommendationsScrollController.updateMarginAndPadding();
     }
 
     @Override
@@ -255,12 +276,6 @@
                 contentLeft + contentWidth, height);
 
         setTranslationShift(mTranslationShift);
-
-        if (mInitialTabsHeight == 0 && mTabsView != null) {
-            mInitialTabsHeight = mTabsView.getMeasuredHeight();
-        }
-
-        mSearchAndRecommendationsScrollController.updateMarginAndPadding();
     }
 
     @Override
@@ -275,6 +290,8 @@
         AdapterHolder primaryUserAdapterHolder = mAdapters.get(AdapterHolder.PRIMARY);
         primaryUserAdapterHolder.setup(findViewById(R.id.primary_widgets_list_view));
         primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
+        updateNoWidgetsView(primaryUserAdapterHolder);
+
         if (mHasWorkProfile) {
             AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
             workUserAdapterHolder.setup(findViewById(R.id.work_widgets_list_view));
@@ -371,7 +388,14 @@
         // No need to check work profile here because mInitialTabHeight is always 0 if there is no
         // work profile.
         return mInitialTabsHeight
-                + mSearchAndRecommendationViewHolder.mContainer.getMeasuredHeight();
+                + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mContainer);
+    }
+
+    /** private the height, in pixel, + the vertical margins of a given view. */
+    private static int measureHeightWithVerticalMargins(View view) {
+        MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
+        return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
+                + marginLayoutParams.topMargin;
     }
 
     /** A holder class for holding adapters & their corresponding recycler view. */
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index 823fb7b..070a9aa 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -157,14 +157,29 @@
     private void setTitles(WidgetsListHeaderEntry entry) {
         mTitle.setText(entry.mPkgItem.title);
 
-        if (entry.widgetsCount > 0) {
-            Resources resources = getContext().getResources();
-            mSubtitle.setText(resources.getQuantityString(R.plurals.widgets_tray_subtitle,
-                    entry.widgetsCount, entry.widgetsCount));
-            mSubtitle.setVisibility(VISIBLE);
-        } else {
+        Resources resources = getContext().getResources();
+        if (entry.widgetsCount == 0 && entry.shortcutsCount == 0) {
             mSubtitle.setVisibility(GONE);
+            return;
         }
+
+        String subtitle;
+        if (entry.widgetsCount > 0 && entry.shortcutsCount > 0) {
+            String widgetsCount = resources.getQuantityString(R.plurals.widgets_count,
+                    entry.widgetsCount, entry.widgetsCount);
+            String shortcutsCount = resources.getQuantityString(R.plurals.shortcuts_count,
+                    entry.shortcutsCount, entry.shortcutsCount);
+            subtitle = resources.getString(R.string.widgets_and_shortcuts_count, widgetsCount,
+                    shortcutsCount);
+        } else if (entry.widgetsCount > 0) {
+            subtitle = resources.getQuantityString(R.plurals.widgets_count,
+                     entry.widgetsCount, entry.widgetsCount);
+        } else {
+            subtitle = resources.getQuantityString(R.plurals.shortcuts_count,
+                    entry.shortcutsCount, entry.shortcutsCount);
+        }
+        mSubtitle.setText(subtitle);
+        mSubtitle.setVisibility(VISIBLE);
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index 9c8684a..47fa71a 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -30,6 +31,7 @@
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.recyclerview.ViewHolderBinder;
 import com.android.launcher3.widget.WidgetCell;
+import com.android.launcher3.widget.WidgetImageView;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.util.WidgetsTableUtils;
 
@@ -144,6 +146,7 @@
                 tableRow = (TableRow) table.getChildAt(i);
             } else {
                 tableRow = new TableRow(table.getContext());
+                tableRow.setGravity(Gravity.CENTER_VERTICAL);
                 table.addView(tableRow);
             }
             if (tableRow.getChildCount() > widgetItems.size()) {
@@ -155,8 +158,9 @@
                     WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
                             R.layout.widget_cell, tableRow, false);
                     // set up touch.
-                    widget.setOnClickListener(mIconClickListener);
-                    widget.setOnLongClickListener(mIconLongClickListener);
+                    WidgetImageView preview = widget.findViewById(R.id.widget_preview);
+                    preview.setOnClickListener(mIconClickListener);
+                    preview.setOnLongClickListener(mIconLongClickListener);
                     tableRow.addView(widget);
                 }
             }
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsPickerSearchPipeline.java b/src/com/android/launcher3/widget/picker/search/WidgetsPickerSearchPipeline.java
new file mode 100644
index 0000000..d12782c
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsPickerSearchPipeline.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.widget.picker.search;
+
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * An interface for a pipeline to handle widgets search.
+ */
+public interface WidgetsPickerSearchPipeline {
+
+    /**
+     * Performs a search query asynchronically. Invokes {@code callback} when the search is
+     * complete.
+     */
+    void query(String input, Consumer<List<WidgetsListBaseEntry>> callback);
+
+    /**
+     * Cancels any ongoing search request.
+     */
+    default void cancel() {};
+
+    /**
+     * Cleans up after search is no longer needed.
+     */
+    default void destroy() {};
+}
diff --git a/src_build_config/BuildConfig.java b/src_build_config/com/android/launcher3/BuildConfig.java
similarity index 100%
rename from src_build_config/BuildConfig.java
rename to src_build_config/com/android/launcher3/BuildConfig.java
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
index 73b1601..abce2a2 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
@@ -48,7 +48,7 @@
     @Override
     public void bindWidgets() {
         final List<WidgetsListBaseEntry> widgets =
-                mBgDataModel.widgetsModel.getWidgetsList(mApp.getContext());
+                mBgDataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext());
         executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor);
     }
 }
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index a8c7c48..3ea4766 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -5,6 +5,8 @@
 
 import static com.android.launcher3.pm.ShortcutConfigActivityInfo.queryList;
 
+import static java.util.stream.Collectors.toList;
+
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.Context;
@@ -67,7 +69,7 @@
      *
      * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
      */
-    public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsList(Context context) {
+    public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsListForPicker(Context context) {
         ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
         AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
 
@@ -82,6 +84,22 @@
         return result;
     }
 
+    /** Returns a mapping of packages to their widgets without static shortcuts. */
+    public synchronized Map<PackageUserKey, List<WidgetItem>> getAllWidgetsWithoutShortcuts() {
+        Map<PackageUserKey, List<WidgetItem>> packagesToWidgets = new HashMap<>();
+        mWidgetsList.forEach((packageItemInfo, widgetsAndShortcuts) -> {
+            List<WidgetItem> widgets = widgetsAndShortcuts.stream()
+                        .filter(item -> item.widgetInfo != null)
+                        .collect(toList());
+            if (widgets.size() > 0) {
+                packagesToWidgets.put(
+                        new PackageUserKey(packageItemInfo.packageName, packageItemInfo.user),
+                        widgets);
+            }
+        });
+        return packagesToWidgets;
+    }
+
     /**
      * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
      *                    only widgets and shortcuts associated with the package/user are.
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index 5e41d43d..a6c7c03 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -17,6 +17,8 @@
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
+import static com.android.launcher3.tapl.LauncherInstrumentation.LONG_WAIT_TIME_MS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -77,6 +79,17 @@
         mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
     }
 
+    @After
+    public void resumeAppStoreUpdate() {
+        executeOnLauncher(launcher -> {
+            if (launcher == null || launcher.getAppsView() == null) {
+                return;
+            }
+            launcher.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
+            Log.d(TestProtocol.WORK_PROFILE_REMOVED, "resuming AppStore updates");
+        });
+    }
+
     @Test
     public void workTabExists() {
         mDevice.pressHome();
@@ -128,9 +141,13 @@
                 WorkEduView.KEY_WORK_EDU_STEP).remove(
                 WorkEduView.KEY_LEGACY_WORK_EDU_SEEN).commit());
 
-        waitForLauncherCondition("Work tab not setup",
-                launcher -> launcher.getAppsView().getContentView() instanceof AllAppsPagedView,
-                60000);
+        waitForLauncherCondition("Work tab not setup", launcher -> {
+            if (launcher.getAppsView().getContentView() instanceof AllAppsPagedView) {
+                launcher.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
+                return true;
+            }
+            return false;
+        }, LONG_WAIT_TIME_MS);
 
         executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
         WorkEduView workEduView = getEduView();
@@ -141,10 +158,6 @@
             workEduView.findViewById(R.id.proceed).callOnClick();
         });
 
-        executeOnLauncher(launcher -> Log.d(TestProtocol.WORK_PROFILE_REMOVED,
-                "work profile status (" + mProfileUserId + ") :"
-                        + launcher.getAppsView().isWorkTabVisible()));
-
         AtomicInteger attempt = new AtomicInteger(0);
         // verify work edu is seen next
         waitForLauncherCondition("Launcher did not show the next edu screen", l -> {
@@ -157,11 +170,10 @@
             if (((AllAppsPagedView) l.getAppsView().getContentView()).getCurrentPage()
                     != WORK_PAGE) {
                 Log.d(TestProtocol.WORK_PROFILE_REMOVED, "Work page not highlighted");
-                return false;
             }
             return ((TextView) workEduView.findViewById(R.id.content_text)).getText().equals(
                     l.getResources().getString(R.string.work_profile_edu_work_apps));
-        }, 60000);
+        });
     }
 
     @Test